vba-runner 0.1.0-alpha.0 → 0.1.1-alpha.1
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/dist/bin/vba-analyzer.cjs +181 -91
- package/dist/bin/vba-formatter.cjs +39 -12
- package/dist/bin/vba-parse-check.cjs +207 -115
- package/dist/bin/vba-run.cjs +386 -235
- package/dist/bin/vba-runner.cjs +12631 -0
- package/dist/lib.cjs +220 -96
- package/package.json +3 -2
|
@@ -41,6 +41,7 @@ __export(vba_analyzer_exports, {
|
|
|
41
41
|
formatWorkspaceOutlineForMcp: () => formatWorkspaceOutline,
|
|
42
42
|
formatWorkspaceReportForMcp: () => formatWorkspaceReportForMcp,
|
|
43
43
|
formatWorkspaceSummary: () => formatWorkspaceSummary,
|
|
44
|
+
main: () => main,
|
|
44
45
|
paramName: () => paramName,
|
|
45
46
|
vbaTypeToTs: () => vbaTypeToTs
|
|
46
47
|
});
|
|
@@ -55,6 +56,8 @@ var LexError = class extends Error {
|
|
|
55
56
|
this.name = "LexError";
|
|
56
57
|
}
|
|
57
58
|
};
|
|
59
|
+
var NUMERIC_TYPE_SUFFIXES = /* @__PURE__ */ new Set(["%", "&", "^", "!", "#", "@"]);
|
|
60
|
+
var IDENTIFIER_TYPE_SUFFIXES = /* @__PURE__ */ new Set(["%", "&", "^", "!", "#", "@", "$"]);
|
|
58
61
|
var Lexer = class _Lexer {
|
|
59
62
|
input = "";
|
|
60
63
|
pos = 0;
|
|
@@ -248,6 +251,7 @@ var Lexer = class _Lexer {
|
|
|
248
251
|
while (/[0-9a-f]/i.test(this.peek())) {
|
|
249
252
|
hexStr += this.advance();
|
|
250
253
|
}
|
|
254
|
+
if (NUMERIC_TYPE_SUFFIXES.has(this.peek())) this.advance();
|
|
251
255
|
return { type: 1 /* Number */, value: "0x" + hexStr, line: startLine, column: startColumn };
|
|
252
256
|
} else if (next === "o" || this.isDigit(next)) {
|
|
253
257
|
if (next === "o") this.advance();
|
|
@@ -255,6 +259,7 @@ var Lexer = class _Lexer {
|
|
|
255
259
|
while (/[0-7]/.test(this.peek())) {
|
|
256
260
|
octStr += this.advance();
|
|
257
261
|
}
|
|
262
|
+
if (NUMERIC_TYPE_SUFFIXES.has(this.peek())) this.advance();
|
|
258
263
|
return { type: 1 /* Number */, value: "0o" + octStr, line: startLine, column: startColumn };
|
|
259
264
|
}
|
|
260
265
|
return { type: 117 /* OperatorAmpersand */, value: "&", line: startLine, column: startColumn };
|
|
@@ -321,8 +326,7 @@ var Lexer = class _Lexer {
|
|
|
321
326
|
numStr += this.advance();
|
|
322
327
|
}
|
|
323
328
|
}
|
|
324
|
-
|
|
325
|
-
if (["%", "&", "@", "!", "#", "^"].indexOf(peekChar) !== -1) {
|
|
329
|
+
if (NUMERIC_TYPE_SUFFIXES.has(this.peek())) {
|
|
326
330
|
numStr += this.advance();
|
|
327
331
|
}
|
|
328
332
|
return { type: 1 /* Number */, value: numStr, line: startLine, column: startColumn };
|
|
@@ -332,9 +336,12 @@ var Lexer = class _Lexer {
|
|
|
332
336
|
while (this.isAlphaNumeric(this.peek())) {
|
|
333
337
|
idStr += this.advance();
|
|
334
338
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
339
|
+
{
|
|
340
|
+
const nextCh = this.peek();
|
|
341
|
+
const charAfterNext = this.pos + 1 < this.input.length ? this.input[this.pos + 1] : "\0";
|
|
342
|
+
if (IDENTIFIER_TYPE_SUFFIXES.has(nextCh) && !((nextCh === "!" || nextCh === "^") && (this.isAlphaNumeric(charAfterNext) || charAfterNext === "_"))) {
|
|
343
|
+
idStr += this.advance();
|
|
344
|
+
}
|
|
338
345
|
}
|
|
339
346
|
const lowerId = idStr.toLowerCase();
|
|
340
347
|
const lowerBase = lowerId.replace(/[$%&#@]$/, "");
|
|
@@ -585,25 +592,46 @@ var Parser = class _Parser {
|
|
|
585
592
|
..._Parser.CONTEXTUAL_KW_STMT_ABSENT,
|
|
586
593
|
..._Parser.CONTEXTUAL_KW_STRUCTURAL
|
|
587
594
|
]);
|
|
588
|
-
/** <statement-keyword> tokens
|
|
589
|
-
*
|
|
590
|
-
*
|
|
591
|
-
*
|
|
595
|
+
/** <statement-keyword> tokens that are <reserved-identifier> per §3.3.5.2 but whose
|
|
596
|
+
* enum values fall in [KeywordBase, KeywordAddressOf].
|
|
597
|
+
* These CANNOT be used as module-level procedure names (parseProcedureDeclaration rejects them).
|
|
598
|
+
* They ARE permitted as member names in expression context (obj.Print, ws.Get, obj.Open) via
|
|
599
|
+
* parsePrimary, which is why they appear in COMPAT_KW_EXPR. */
|
|
600
|
+
static STATEMENT_KW_RESERVED = /* @__PURE__ */ new Set([
|
|
601
|
+
81 /* KeywordOpen */,
|
|
602
|
+
82 /* KeywordClose */,
|
|
603
|
+
84 /* KeywordInput */,
|
|
604
|
+
85 /* KeywordPrint */,
|
|
605
|
+
86 /* KeywordPut */,
|
|
606
|
+
92 /* KeywordWrite */,
|
|
607
|
+
93 /* KeywordLock */,
|
|
608
|
+
98 /* KeywordSeek */,
|
|
609
|
+
100 /* KeywordUnlock */,
|
|
610
|
+
102 /* KeywordEvent */,
|
|
611
|
+
103 /* KeywordRaiseEvent */,
|
|
612
|
+
105 /* KeywordImplements */
|
|
613
|
+
]);
|
|
614
|
+
/** <statement-keyword> tokens permitted as member names in parsePrimary (obj.Print, ws.Get,
|
|
615
|
+
* obj.Open, etc.) per §3.3.5.2 "unrestricted-name" rule.
|
|
616
|
+
* In parseStatementInner these keywords are all dispatched unconditionally before the
|
|
617
|
+
* identifier branch, so the COMPAT_KW_EXPR entries there are never reached — they exist
|
|
618
|
+
* solely for parsePrimary dot-member-access context. */
|
|
592
619
|
static COMPAT_KW_EXPR = /* @__PURE__ */ new Set([
|
|
593
620
|
98 /* KeywordSeek */,
|
|
594
|
-
// <statement-keyword>
|
|
595
621
|
84 /* KeywordInput */,
|
|
596
|
-
// <statement-keyword> / <special-form>
|
|
597
622
|
85 /* KeywordPrint */,
|
|
598
|
-
// <statement-keyword>
|
|
599
623
|
86 /* KeywordPut */,
|
|
600
|
-
// <statement-keyword>
|
|
601
624
|
20 /* KeywordGet */,
|
|
602
|
-
// <
|
|
625
|
+
// enum < KeywordBase so also rejected by range check in parseProcedureDeclaration
|
|
603
626
|
93 /* KeywordLock */,
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
627
|
+
100 /* KeywordUnlock */,
|
|
628
|
+
81 /* KeywordOpen */,
|
|
629
|
+
82 /* KeywordClose */,
|
|
630
|
+
// Non-reserved statement keywords: can be user-defined proc names (e.g. Function Kill()).
|
|
631
|
+
// Included here so "Kill = v", "Reset = v", "Width = v" fall through to identifier branch.
|
|
632
|
+
97 /* KeywordKill */,
|
|
633
|
+
99 /* KeywordReset */,
|
|
634
|
+
109 /* KeywordWidth */
|
|
607
635
|
]);
|
|
608
636
|
errorRecovery;
|
|
609
637
|
/** Returns true if token is a valid IDENTIFIER per §3.3.5.2:
|
|
@@ -620,6 +648,15 @@ var Parser = class _Parser {
|
|
|
620
648
|
isNameToken(token) {
|
|
621
649
|
return token.type === 0 /* Identifier */ || token.type === 138 /* ForeignName */ || token.type >= 3 /* KeywordFor */ && token.type <= 110 /* KeywordAddressOf */;
|
|
622
650
|
}
|
|
651
|
+
/**
|
|
652
|
+
* Returns true if the current token stream contains a file-I/O Open statement pattern
|
|
653
|
+
* ("For <file-mode>") on the current logical line.
|
|
654
|
+
* Open "path" For Input|Output|Append|Random|Binary ... As #n → true
|
|
655
|
+
* Open() / Open = value / Open x (user-defined call) → false
|
|
656
|
+
*
|
|
657
|
+
* This syntactic check lets the parser disambiguate without a pre-scan:
|
|
658
|
+
* VBA §3.3.5.3 — user-defined procedures (priority 2) > built-in statement keywords (priority 3).
|
|
659
|
+
*/
|
|
623
660
|
recordError(message, token) {
|
|
624
661
|
const pos = { line: token.line, column: token.column };
|
|
625
662
|
this._diagnostics.push({ message, loc: { start: pos, end: pos }, severity: "error" });
|
|
@@ -699,7 +736,7 @@ var Parser = class _Parser {
|
|
|
699
736
|
isByVal = this.advance().type === 47 /* KeywordByVal */;
|
|
700
737
|
}
|
|
701
738
|
const token = this.peek();
|
|
702
|
-
if (token.type !== 0 /* Identifier */ && (token.type < 79 /* KeywordBase */ || token.type > 110 /* KeywordAddressOf */)) {
|
|
739
|
+
if (token.type !== 0 /* Identifier */ && !_Parser.CONTEXTUAL_KW.has(token.type) && (token.type < 79 /* KeywordBase */ || token.type > 110 /* KeywordAddressOf */)) {
|
|
703
740
|
this.throwError(`Parse error at line ${token.line}: Expected parameter name (Found ${this.tokenDisplay(token.value)})`);
|
|
704
741
|
}
|
|
705
742
|
const nameToken = this.advance();
|
|
@@ -1110,8 +1147,75 @@ var Parser = class _Parser {
|
|
|
1110
1147
|
}
|
|
1111
1148
|
return stmt;
|
|
1112
1149
|
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Parse an identifier, method call, or assignment statement.
|
|
1152
|
+
* Called from the identifier branch of parseStatementInner AND from the ARCH-1
|
|
1153
|
+
* user-proc override path (when a built-in keyword is user-defined as a procedure).
|
|
1154
|
+
*/
|
|
1155
|
+
parseIdentifierOrCallStatement() {
|
|
1156
|
+
const token = this.peek();
|
|
1157
|
+
if ((token.type === 0 /* Identifier */ || _Parser.CONTEXTUAL_KW.has(token.type)) && this.pos + 1 < this.tokens.length && this.tokens[this.pos + 1].type === 130 /* OperatorColon */) {
|
|
1158
|
+
const labelName = token.value;
|
|
1159
|
+
this.advance();
|
|
1160
|
+
this.advance();
|
|
1161
|
+
return { type: "LabelStatement", label: labelName };
|
|
1162
|
+
}
|
|
1163
|
+
if (token.type === 1 /* Number */) {
|
|
1164
|
+
const labelName = token.value;
|
|
1165
|
+
this.advance();
|
|
1166
|
+
this.match(130 /* OperatorColon */);
|
|
1167
|
+
return { type: "LabelStatement", label: labelName };
|
|
1168
|
+
}
|
|
1169
|
+
const savedPos = this.pos;
|
|
1170
|
+
const expr = this.parsePrimary();
|
|
1171
|
+
if (this.match(120 /* OperatorEquals */)) {
|
|
1172
|
+
return {
|
|
1173
|
+
type: "AssignmentStatement",
|
|
1174
|
+
left: expr,
|
|
1175
|
+
right: this.parseExpression()
|
|
1176
|
+
};
|
|
1177
|
+
} else {
|
|
1178
|
+
if (expr.type === "CallExpression" && this.isBinaryOnlyOperator(this.peek().type)) {
|
|
1179
|
+
this.pos = savedPos;
|
|
1180
|
+
const callee = this.parsePrimary(
|
|
1181
|
+
/* stopBeforeSpacedLParen= */
|
|
1182
|
+
true
|
|
1183
|
+
);
|
|
1184
|
+
const args2 = [this.parseCallArgument()];
|
|
1185
|
+
while (this.match(126 /* OperatorComma */)) {
|
|
1186
|
+
args2.push(this.parseCallArgument());
|
|
1187
|
+
}
|
|
1188
|
+
return { type: "CallStatement", expression: { type: "CallExpression", callee, args: args2 } };
|
|
1189
|
+
}
|
|
1190
|
+
const args = [];
|
|
1191
|
+
if (this.peek().type !== 135 /* Newline */ && this.peek().type !== 136 /* EOF */ && this.peek().type !== 9 /* KeywordElse */ && this.peek().type !== 8 /* KeywordElseIf */ && this.peek().type !== 10 /* KeywordEnd */ && this.peek().type !== 5 /* KeywordNext */ && this.peek().type !== 14 /* KeywordLoop */) {
|
|
1192
|
+
args.push(this.parseCallArgument());
|
|
1193
|
+
while (this.match(126 /* OperatorComma */)) {
|
|
1194
|
+
args.push(this.parseCallArgument());
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
if (args.length > 0) {
|
|
1198
|
+
return { type: "CallStatement", expression: { type: "CallExpression", callee: expr, args } };
|
|
1199
|
+
} else if (expr.type === "CallExpression") {
|
|
1200
|
+
const callExpr = expr;
|
|
1201
|
+
if (callExpr.args.length === 0 && callExpr.callee.type === "Identifier" && !callExpr.callee.foreign) {
|
|
1202
|
+
const line = callExpr.loc?.start.line ?? this.peek().line;
|
|
1203
|
+
this.throwError(`Parse error: syntax error at line ${line}`);
|
|
1204
|
+
}
|
|
1205
|
+
return { type: "CallStatement", expression: callExpr };
|
|
1206
|
+
} else {
|
|
1207
|
+
return { type: "CallStatement", expression: { type: "CallExpression", callee: expr, args: [] } };
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1113
1211
|
parseStatementInner() {
|
|
1114
1212
|
const token = this.peek();
|
|
1213
|
+
if (_Parser.CONTEXTUAL_KW.has(token.type) && this.pos + 1 < this.tokens.length && this.tokens[this.pos + 1].type === 130 /* OperatorColon */) {
|
|
1214
|
+
const labelName = token.value;
|
|
1215
|
+
this.advance();
|
|
1216
|
+
this.advance();
|
|
1217
|
+
return { type: "LabelStatement", label: labelName };
|
|
1218
|
+
}
|
|
1115
1219
|
if (token.type === 57 /* KeywordPublic */ || token.type === 58 /* KeywordPrivate */ || token.type === 60 /* KeywordFriend */) {
|
|
1116
1220
|
const scope = this.advance().value.toLowerCase();
|
|
1117
1221
|
const next = this.peek();
|
|
@@ -1138,6 +1242,9 @@ var Parser = class _Parser {
|
|
|
1138
1242
|
stmt2.scope = scope;
|
|
1139
1243
|
return stmt2;
|
|
1140
1244
|
}
|
|
1245
|
+
if (next.type === 102 /* KeywordEvent */) {
|
|
1246
|
+
return this.parseEventDeclaration(scope);
|
|
1247
|
+
}
|
|
1141
1248
|
const stmt = this.parseDimStatement(false, true);
|
|
1142
1249
|
if (stmt) {
|
|
1143
1250
|
stmt.scope = scope;
|
|
@@ -1259,7 +1366,7 @@ var Parser = class _Parser {
|
|
|
1259
1366
|
return this.parsePrintStatement();
|
|
1260
1367
|
} else if (token.type === 86 /* KeywordPut */) {
|
|
1261
1368
|
return this.parsePutStatement();
|
|
1262
|
-
} else if (token.type === 20 /* KeywordGet */) {
|
|
1369
|
+
} else if (token.type === 20 /* KeywordGet */ && this.peek(1).type === 111 /* OperatorHash */) {
|
|
1263
1370
|
return this.parseGetStatement();
|
|
1264
1371
|
} else if (token.type === 84 /* KeywordInput */) {
|
|
1265
1372
|
return this.parseInputStatement();
|
|
@@ -1279,7 +1386,7 @@ var Parser = class _Parser {
|
|
|
1279
1386
|
return this.parseLockStatement();
|
|
1280
1387
|
} else if (token.type === 100 /* KeywordUnlock */) {
|
|
1281
1388
|
return this.parseUnlockStatement();
|
|
1282
|
-
} else if (token.type === 109 /* KeywordWidth */ && this.peek(1).type
|
|
1389
|
+
} else if (token.type === 109 /* KeywordWidth */ && this.peek(1).type === 111 /* OperatorHash */) {
|
|
1283
1390
|
return this.parseWidthStatement();
|
|
1284
1391
|
} else if (token.type === 69 /* KeywordClass */ && this.peek(1).type !== 120 /* OperatorEquals */) {
|
|
1285
1392
|
return this.parseClassDeclaration();
|
|
@@ -1292,59 +1399,8 @@ var Parser = class _Parser {
|
|
|
1292
1399
|
return { type: "CallStatement", expression: { type: "CallExpression", callee: expr, args: [] } };
|
|
1293
1400
|
}
|
|
1294
1401
|
this.throwError(`Parse error: Expected procedure call after 'Call'`);
|
|
1295
|
-
} else if (token.type === 0 /* Identifier */ || token.type === 138 /* ForeignName */ || token.type === 129 /* OperatorDot */ || token.type === 1 /* Number */ || _Parser.CONTEXTUAL_KW.has(token.type)) {
|
|
1296
|
-
|
|
1297
|
-
const labelName = token.value;
|
|
1298
|
-
this.advance();
|
|
1299
|
-
this.advance();
|
|
1300
|
-
return { type: "LabelStatement", label: labelName };
|
|
1301
|
-
} else if (token.type === 1 /* Number */) {
|
|
1302
|
-
const labelName = token.value;
|
|
1303
|
-
this.advance();
|
|
1304
|
-
this.match(130 /* OperatorColon */);
|
|
1305
|
-
return { type: "LabelStatement", label: labelName };
|
|
1306
|
-
}
|
|
1307
|
-
const savedPos = this.pos;
|
|
1308
|
-
const expr = this.parsePrimary();
|
|
1309
|
-
if (this.match(120 /* OperatorEquals */)) {
|
|
1310
|
-
return {
|
|
1311
|
-
type: "AssignmentStatement",
|
|
1312
|
-
left: expr,
|
|
1313
|
-
right: this.parseExpression()
|
|
1314
|
-
};
|
|
1315
|
-
} else {
|
|
1316
|
-
if (expr.type === "CallExpression" && this.isBinaryOnlyOperator(this.peek().type)) {
|
|
1317
|
-
this.pos = savedPos;
|
|
1318
|
-
const callee = this.parsePrimary(
|
|
1319
|
-
/* stopBeforeSpacedLParen= */
|
|
1320
|
-
true
|
|
1321
|
-
);
|
|
1322
|
-
const args2 = [this.parseCallArgument()];
|
|
1323
|
-
while (this.match(126 /* OperatorComma */)) {
|
|
1324
|
-
args2.push(this.parseCallArgument());
|
|
1325
|
-
}
|
|
1326
|
-
return { type: "CallStatement", expression: { type: "CallExpression", callee, args: args2 } };
|
|
1327
|
-
}
|
|
1328
|
-
const args = [];
|
|
1329
|
-
if (this.peek().type !== 135 /* Newline */ && this.peek().type !== 136 /* EOF */ && this.peek().type !== 9 /* KeywordElse */ && this.peek().type !== 8 /* KeywordElseIf */ && this.peek().type !== 10 /* KeywordEnd */ && this.peek().type !== 5 /* KeywordNext */ && this.peek().type !== 14 /* KeywordLoop */) {
|
|
1330
|
-
args.push(this.parseCallArgument());
|
|
1331
|
-
while (this.match(126 /* OperatorComma */)) {
|
|
1332
|
-
args.push(this.parseCallArgument());
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
if (args.length > 0) {
|
|
1336
|
-
return { type: "CallStatement", expression: { type: "CallExpression", callee: expr, args } };
|
|
1337
|
-
} else if (expr.type === "CallExpression") {
|
|
1338
|
-
const callExpr = expr;
|
|
1339
|
-
if (callExpr.args.length === 0 && callExpr.callee.type === "Identifier" && !callExpr.callee.foreign) {
|
|
1340
|
-
const line = callExpr.loc?.start.line ?? this.peek().line;
|
|
1341
|
-
this.throwError(`Parse error: syntax error at line ${line}`);
|
|
1342
|
-
}
|
|
1343
|
-
return { type: "CallStatement", expression: callExpr };
|
|
1344
|
-
} else {
|
|
1345
|
-
return { type: "CallStatement", expression: { type: "CallExpression", callee: expr, args: [] } };
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1402
|
+
} else if (token.type === 0 /* Identifier */ || token.type === 138 /* ForeignName */ || token.type === 129 /* OperatorDot */ || token.type === 70 /* KeywordMe */ || token.type === 1 /* Number */ || _Parser.CONTEXTUAL_KW.has(token.type) || _Parser.COMPAT_KW_EXPR.has(token.type)) {
|
|
1403
|
+
return this.parseIdentifierOrCallStatement();
|
|
1348
1404
|
} else if (token.type === 137 /* Unknown */) {
|
|
1349
1405
|
this.throwError(`Parse error: Unknown token '${this.tokenDisplay(token.value)}' at line ${token.line}`);
|
|
1350
1406
|
} else {
|
|
@@ -1352,7 +1408,7 @@ var Parser = class _Parser {
|
|
|
1352
1408
|
}
|
|
1353
1409
|
return null;
|
|
1354
1410
|
}
|
|
1355
|
-
parseProcedureDeclaration(scope, isStatic) {
|
|
1411
|
+
parseProcedureDeclaration(scope, isStatic, isClassMember = false) {
|
|
1356
1412
|
const isFunction = this.peek().type === 18 /* KeywordFunction */;
|
|
1357
1413
|
const isProperty = this.peek().type === 19 /* KeywordProperty */;
|
|
1358
1414
|
this.advance();
|
|
@@ -1370,6 +1426,9 @@ var Parser = class _Parser {
|
|
|
1370
1426
|
if (!this.isIdentifier(idToken) && (idToken.type < 79 /* KeywordBase */ || idToken.type > 110 /* KeywordAddressOf */)) {
|
|
1371
1427
|
this.throwError(`Parse error at line ${idToken.line}: Expected procedure name (Found ${this.tokenDisplay(idToken.value)})`);
|
|
1372
1428
|
}
|
|
1429
|
+
if (!isClassMember && _Parser.STATEMENT_KW_RESERVED.has(idToken.type)) {
|
|
1430
|
+
this.throwError(`Compile error at line ${idToken.line}: '${idToken.value}' is a reserved word and cannot be used as a procedure name`);
|
|
1431
|
+
}
|
|
1373
1432
|
const name = this.makeIdentifier(idToken);
|
|
1374
1433
|
const parameters = [];
|
|
1375
1434
|
let paramsEndColumn;
|
|
@@ -1532,7 +1591,7 @@ var Parser = class _Parser {
|
|
|
1532
1591
|
parseGoToStatement() {
|
|
1533
1592
|
this.advance();
|
|
1534
1593
|
const labelToken = this.advance();
|
|
1535
|
-
if (labelToken
|
|
1594
|
+
if (!this.isIdentifier(labelToken) && labelToken.type !== 1 /* Number */) {
|
|
1536
1595
|
this.throwError(`Parse error: Expected identifier or number after 'GoTo' at line ${labelToken.line}`);
|
|
1537
1596
|
}
|
|
1538
1597
|
return { type: "GoToStatement", label: labelToken.value };
|
|
@@ -1655,7 +1714,7 @@ var Parser = class _Parser {
|
|
|
1655
1714
|
parseClassDeclaration() {
|
|
1656
1715
|
this.advance();
|
|
1657
1716
|
const nameToken = this.advance();
|
|
1658
|
-
if (nameToken
|
|
1717
|
+
if (!this.isIdentifier(nameToken)) {
|
|
1659
1718
|
this.throwError(`Parse error: Expected class name after 'Class' at line ${nameToken.line}`);
|
|
1660
1719
|
}
|
|
1661
1720
|
return this.parseClassBody(nameToken.value, true);
|
|
@@ -1681,7 +1740,7 @@ var Parser = class _Parser {
|
|
|
1681
1740
|
}
|
|
1682
1741
|
const inner = this.peek();
|
|
1683
1742
|
if (inner.type === 17 /* KeywordSub */ || inner.type === 18 /* KeywordFunction */ || inner.type === 19 /* KeywordProperty */) {
|
|
1684
|
-
const proc = this.parseProcedureDeclaration(scope);
|
|
1743
|
+
const proc = this.parseProcedureDeclaration(scope, void 0, true);
|
|
1685
1744
|
proc.moduleName = className;
|
|
1686
1745
|
procedures.push(proc);
|
|
1687
1746
|
body.push(proc);
|
|
@@ -1697,13 +1756,16 @@ var Parser = class _Parser {
|
|
|
1697
1756
|
} else if (inner.type === 102 /* KeywordEvent */) {
|
|
1698
1757
|
const event = this.parseEventDeclaration(scope);
|
|
1699
1758
|
body.push(event);
|
|
1759
|
+
} else if (inner.type === 31 /* KeywordConst */) {
|
|
1760
|
+
const constDecl = this.parseConstDeclaration();
|
|
1761
|
+
body.push(constDecl);
|
|
1700
1762
|
} else if (inner.type === 68 /* KeywordStatic */) {
|
|
1701
1763
|
this.advance();
|
|
1702
1764
|
const field = this.parseDimStatement(true, true);
|
|
1703
1765
|
field.scope = scope ?? "public";
|
|
1704
1766
|
fields.push(field);
|
|
1705
1767
|
body.push(field);
|
|
1706
|
-
} else if (scope !== void 0 && inner.type ===
|
|
1768
|
+
} else if (scope !== void 0 && (this.isIdentifier(inner) || inner.type === 104 /* KeywordWithEvents */)) {
|
|
1707
1769
|
const field = this.parseDimStatement(false, true);
|
|
1708
1770
|
field.scope = scope;
|
|
1709
1771
|
fields.push(field);
|
|
@@ -2182,7 +2244,8 @@ var Parser = class _Parser {
|
|
|
2182
2244
|
} else if (token.type === 0 /* Identifier */ || _Parser.CONTEXTUAL_KW.has(token.type) || _Parser.COMPAT_KW_EXPR.has(token.type)) {
|
|
2183
2245
|
expr = { type: "Identifier", name: token.value };
|
|
2184
2246
|
} else if (token.type === 110 /* KeywordAddressOf */) {
|
|
2185
|
-
const procName = this.
|
|
2247
|
+
const procName = this.advance();
|
|
2248
|
+
if (!this.isIdentifier(procName)) this.throwError(`Parse error at line ${procName.line}: Expected procedure name after 'AddressOf'`);
|
|
2186
2249
|
expr = { type: "AddressOfExpression", procedureName: { type: "Identifier", name: procName.value } };
|
|
2187
2250
|
} else if (token.type === 44 /* KeywordEmpty */) {
|
|
2188
2251
|
expr = { type: "Identifier", name: token.value };
|
|
@@ -2217,7 +2280,7 @@ var Parser = class _Parser {
|
|
|
2217
2280
|
this.throwError(`Parse error: Expected 'Is' after 'TypeOf' at line ${this.peek().line}`);
|
|
2218
2281
|
}
|
|
2219
2282
|
const typeToken = this.advance();
|
|
2220
|
-
if (typeToken
|
|
2283
|
+
if (!this.isIdentifier(typeToken) && typeToken.type !== 25 /* KeywordCollection */) {
|
|
2221
2284
|
this.throwError(`Parse error: Expected type name after 'Is' at line ${typeToken.line}`);
|
|
2222
2285
|
}
|
|
2223
2286
|
expr = { type: "TypeOfIsExpression", expression: expr, typeName: typeToken.value };
|
|
@@ -2306,7 +2369,7 @@ var Parser = class _Parser {
|
|
|
2306
2369
|
const labels = [];
|
|
2307
2370
|
while (true) {
|
|
2308
2371
|
const labelToken = this.advance();
|
|
2309
|
-
if (labelToken
|
|
2372
|
+
if (!this.isIdentifier(labelToken) && labelToken.type !== 1 /* Number */) {
|
|
2310
2373
|
this.throwError(`Parse error: Expected label (identifier or number) in On...GoTo/GoSub at line ${labelToken.line}`);
|
|
2311
2374
|
}
|
|
2312
2375
|
labels.push(labelToken.value);
|
|
@@ -2319,7 +2382,7 @@ var Parser = class _Parser {
|
|
|
2319
2382
|
parseGoSubStatement() {
|
|
2320
2383
|
this.advance();
|
|
2321
2384
|
const labelToken = this.advance();
|
|
2322
|
-
if (labelToken
|
|
2385
|
+
if (!this.isIdentifier(labelToken) && labelToken.type !== 1 /* Number */) {
|
|
2323
2386
|
this.throwError(`Parse error: Expected label after GoSub at line ${labelToken.line}`);
|
|
2324
2387
|
}
|
|
2325
2388
|
return { type: "GoSubStatement", label: labelToken.value };
|
|
@@ -2349,7 +2412,8 @@ var Parser = class _Parser {
|
|
|
2349
2412
|
}
|
|
2350
2413
|
parseEventDeclaration(scope) {
|
|
2351
2414
|
this.advance();
|
|
2352
|
-
const idToken = this.
|
|
2415
|
+
const idToken = this.advance();
|
|
2416
|
+
if (!this.isIdentifier(idToken) && !this.isNameToken(idToken)) this.throwError(`Expected identifier after 'Event' at line ${idToken.line}`);
|
|
2353
2417
|
const name = { type: "Identifier", name: idToken.value };
|
|
2354
2418
|
const parameters = [];
|
|
2355
2419
|
if (this.match(127 /* OperatorLParen */)) {
|
|
@@ -2365,7 +2429,8 @@ var Parser = class _Parser {
|
|
|
2365
2429
|
}
|
|
2366
2430
|
parseRaiseEventStatement() {
|
|
2367
2431
|
this.advance();
|
|
2368
|
-
const idToken = this.
|
|
2432
|
+
const idToken = this.advance();
|
|
2433
|
+
if (!this.isIdentifier(idToken) && !this.isNameToken(idToken)) this.throwError(`Expected identifier after 'RaiseEvent' at line ${idToken.line}`);
|
|
2369
2434
|
const eventName = { type: "Identifier", name: idToken.value };
|
|
2370
2435
|
const args = [];
|
|
2371
2436
|
if (this.match(127 /* OperatorLParen */)) {
|
|
@@ -2381,7 +2446,8 @@ var Parser = class _Parser {
|
|
|
2381
2446
|
}
|
|
2382
2447
|
parseImplementsDirective() {
|
|
2383
2448
|
this.advance();
|
|
2384
|
-
const idToken = this.
|
|
2449
|
+
const idToken = this.advance();
|
|
2450
|
+
if (!this.isIdentifier(idToken)) this.throwError(`Parse error at line ${idToken.line}: Expected interface name after 'Implements'`);
|
|
2385
2451
|
return { type: "ImplementsDirective", interfaceName: idToken.value };
|
|
2386
2452
|
}
|
|
2387
2453
|
parseAppActivateStatement() {
|
|
@@ -2693,6 +2759,11 @@ function evaluateCCExpr(expr, resolve) {
|
|
|
2693
2759
|
// ../../test-libs/vba-analyzer.ts
|
|
2694
2760
|
var fs = __toESM(require("fs"), 1);
|
|
2695
2761
|
var path = __toESM(require("path"), 1);
|
|
2762
|
+
|
|
2763
|
+
// ../../test-libs/version.ts
|
|
2764
|
+
var VERSION = "0.1.1-alpha.1";
|
|
2765
|
+
|
|
2766
|
+
// ../../test-libs/vba-analyzer.ts
|
|
2696
2767
|
function isStatementArray(v) {
|
|
2697
2768
|
return Array.isArray(v);
|
|
2698
2769
|
}
|
|
@@ -4196,7 +4267,8 @@ function analyzeFile(filePath) {
|
|
|
4196
4267
|
definedTypes: /* @__PURE__ */ new Set(),
|
|
4197
4268
|
callsByProc: /* @__PURE__ */ new Map(),
|
|
4198
4269
|
xlVbConstantRefs: /* @__PURE__ */ new Set(),
|
|
4199
|
-
commentedCodeBlocks: detectCommentedCode(src, filePath)
|
|
4270
|
+
commentedCodeBlocks: detectCommentedCode(src, filePath),
|
|
4271
|
+
withEventsVars: /* @__PURE__ */ new Set()
|
|
4200
4272
|
};
|
|
4201
4273
|
}
|
|
4202
4274
|
if (ast.diagnostics && ast.diagnostics.length > 0) {
|
|
@@ -4275,13 +4347,22 @@ function analyzeFile(filePath) {
|
|
|
4275
4347
|
const xlVbConstantRefs = collectExcelConstantRefs(ast);
|
|
4276
4348
|
const gotoGraphs = procs.map((proc) => analyzeGotoInProc(proc));
|
|
4277
4349
|
const loopAnalyses = procs.map((proc, i) => analyzeLoopsInProc(proc, gotoGraphs[i]));
|
|
4350
|
+
const withEventsVars = /* @__PURE__ */ new Set();
|
|
4351
|
+
for (const s of ast.body) {
|
|
4352
|
+
if (s.type === "VariableDeclaration") {
|
|
4353
|
+
for (const decl of s.declarations ?? []) {
|
|
4354
|
+
if (decl.isWithEvents) withEventsVars.add((decl.name?.name ?? "").toLowerCase());
|
|
4355
|
+
}
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4278
4358
|
return {
|
|
4279
4359
|
report: { filePath, totalLines: lines.length, procedureCount: procedures.length, procedures, prefixClusters, warnings, gotoGraphs, loopAnalyses },
|
|
4280
4360
|
definedProcs,
|
|
4281
4361
|
definedTypes,
|
|
4282
4362
|
callsByProc,
|
|
4283
4363
|
xlVbConstantRefs,
|
|
4284
|
-
commentedCodeBlocks: detectCommentedCode(src, filePath)
|
|
4364
|
+
commentedCodeBlocks: detectCommentedCode(src, filePath),
|
|
4365
|
+
withEventsVars
|
|
4285
4366
|
};
|
|
4286
4367
|
}
|
|
4287
4368
|
function buildWorkspaceReport(analyses) {
|
|
@@ -4313,6 +4394,10 @@ function buildWorkspaceReport(analyses) {
|
|
|
4313
4394
|
for (const a of analyses) {
|
|
4314
4395
|
for (const p of a.report.procedures) {
|
|
4315
4396
|
if (p.referenceCount === 0) {
|
|
4397
|
+
const nameLower = p.name.toLowerCase();
|
|
4398
|
+
const sep = nameLower.indexOf("_");
|
|
4399
|
+
const isEventHandler = nameLower === "class_initialize" || nameLower === "class_terminate" || sep > 0 && a.withEventsVars.has(nameLower.slice(0, sep));
|
|
4400
|
+
if (isEventHandler) continue;
|
|
4316
4401
|
if (p.scope === "private") {
|
|
4317
4402
|
deadCodeCandidates.push({
|
|
4318
4403
|
file: a.report.filePath,
|
|
@@ -4977,10 +5062,13 @@ function buildDiffJson(baseline, current) {
|
|
|
4977
5062
|
}
|
|
4978
5063
|
};
|
|
4979
5064
|
}
|
|
4980
|
-
function main() {
|
|
4981
|
-
|
|
5065
|
+
function main(args) {
|
|
5066
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
5067
|
+
console.log(VERSION);
|
|
5068
|
+
process.exit(0);
|
|
5069
|
+
}
|
|
4982
5070
|
if (args.length === 0 || args.includes("--help")) {
|
|
4983
|
-
console.log("Usage:
|
|
5071
|
+
console.log("Usage: vba-runner analyze <file-or-dir> [options]");
|
|
4984
5072
|
console.log("");
|
|
4985
5073
|
console.log(" --json JSON \u5F62\u5F0F\u3067\u51FA\u529B\uFF08\u30D7\u30ED\u30B0\u30E9\u30E0\u9023\u643A\u7528\uFF09");
|
|
4986
5074
|
console.log(" --diff <baseline> baseline JSON \u3068\u306E\u5DEE\u5206\u3092\u8868\u793A\uFF08--json \u3068\u4F75\u7528\u53EF\uFF09");
|
|
@@ -4990,6 +5078,7 @@ function main() {
|
|
|
4990
5078
|
console.log(" --goto-graph GoTo \u6587\u306E\u5236\u5FA1\u30D5\u30ED\u30FC\u30B0\u30E9\u30D5\u3068\u30EA\u30D5\u30A1\u30AF\u30BF\u30EA\u30F3\u30B0\u63D0\u6848\u3092\u8868\u793A");
|
|
4991
5079
|
console.log(" --cross-iter \u30AF\u30ED\u30B9\u30A4\u30C6\u30EC\u30FC\u30B7\u30E7\u30F3\u5909\u6570\uFF08ByRef \u5FC5\u8981\u5909\u6570\uFF09\u306E\u4E00\u89A7\u3092\u8868\u793A");
|
|
4992
5080
|
console.log(" --gen-test-dir <dir> \u30C6\u30B9\u30C8\u7528\u30BD\u30FC\u30B9\u3092\u6307\u5B9A\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306B\u751F\u6210\uFF08const.ts \u7B49\uFF09");
|
|
5081
|
+
console.log(" --version \u30D0\u30FC\u30B8\u30E7\u30F3\u3092\u8868\u793A");
|
|
4993
5082
|
process.exit(args.length === 0 ? 1 : 0);
|
|
4994
5083
|
}
|
|
4995
5084
|
const wantJson = args.includes("--json");
|
|
@@ -5064,7 +5153,7 @@ function main() {
|
|
|
5064
5153
|
);
|
|
5065
5154
|
if (hasParseErrors) process.exit(1);
|
|
5066
5155
|
}
|
|
5067
|
-
if (process.argv[1]?.includes("vba-analyzer")) main();
|
|
5156
|
+
if (process.argv[1]?.includes("vba-analyzer")) main(process.argv.slice(2));
|
|
5068
5157
|
function analyzeWorkspaceForMcpFromFiles(files) {
|
|
5069
5158
|
const analyses = files.map((f) => analyzeFile(f));
|
|
5070
5159
|
return buildWorkspaceReport(analyses);
|
|
@@ -5088,6 +5177,7 @@ function formatWorkspaceReportForMcp(w) {
|
|
|
5088
5177
|
formatWorkspaceOutlineForMcp,
|
|
5089
5178
|
formatWorkspaceReportForMcp,
|
|
5090
5179
|
formatWorkspaceSummary,
|
|
5180
|
+
main,
|
|
5091
5181
|
paramName,
|
|
5092
5182
|
vbaTypeToTs
|
|
5093
5183
|
});
|
|
@@ -5,6 +5,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
8
12
|
var __copyProps = (to, from, except, desc) => {
|
|
9
13
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
14
|
for (let key of __getOwnPropNames(from))
|
|
@@ -21,8 +25,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
25
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
26
|
mod
|
|
23
27
|
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
24
29
|
|
|
25
30
|
// ../../test-libs/vba-formatter.ts
|
|
31
|
+
var vba_formatter_exports = {};
|
|
32
|
+
__export(vba_formatter_exports, {
|
|
33
|
+
main: () => main
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(vba_formatter_exports);
|
|
26
36
|
var fs = __toESM(require("fs"), 1);
|
|
27
37
|
var path = __toESM(require("path"), 1);
|
|
28
38
|
|
|
@@ -35,6 +45,8 @@ var LexError = class extends Error {
|
|
|
35
45
|
this.name = "LexError";
|
|
36
46
|
}
|
|
37
47
|
};
|
|
48
|
+
var NUMERIC_TYPE_SUFFIXES = /* @__PURE__ */ new Set(["%", "&", "^", "!", "#", "@"]);
|
|
49
|
+
var IDENTIFIER_TYPE_SUFFIXES = /* @__PURE__ */ new Set(["%", "&", "^", "!", "#", "@", "$"]);
|
|
38
50
|
var Lexer = class _Lexer {
|
|
39
51
|
input = "";
|
|
40
52
|
pos = 0;
|
|
@@ -228,6 +240,7 @@ var Lexer = class _Lexer {
|
|
|
228
240
|
while (/[0-9a-f]/i.test(this.peek())) {
|
|
229
241
|
hexStr += this.advance();
|
|
230
242
|
}
|
|
243
|
+
if (NUMERIC_TYPE_SUFFIXES.has(this.peek())) this.advance();
|
|
231
244
|
return { type: 1 /* Number */, value: "0x" + hexStr, line: startLine, column: startColumn };
|
|
232
245
|
} else if (next === "o" || this.isDigit(next)) {
|
|
233
246
|
if (next === "o") this.advance();
|
|
@@ -235,6 +248,7 @@ var Lexer = class _Lexer {
|
|
|
235
248
|
while (/[0-7]/.test(this.peek())) {
|
|
236
249
|
octStr += this.advance();
|
|
237
250
|
}
|
|
251
|
+
if (NUMERIC_TYPE_SUFFIXES.has(this.peek())) this.advance();
|
|
238
252
|
return { type: 1 /* Number */, value: "0o" + octStr, line: startLine, column: startColumn };
|
|
239
253
|
}
|
|
240
254
|
return { type: 117 /* OperatorAmpersand */, value: "&", line: startLine, column: startColumn };
|
|
@@ -301,8 +315,7 @@ var Lexer = class _Lexer {
|
|
|
301
315
|
numStr += this.advance();
|
|
302
316
|
}
|
|
303
317
|
}
|
|
304
|
-
|
|
305
|
-
if (["%", "&", "@", "!", "#", "^"].indexOf(peekChar) !== -1) {
|
|
318
|
+
if (NUMERIC_TYPE_SUFFIXES.has(this.peek())) {
|
|
306
319
|
numStr += this.advance();
|
|
307
320
|
}
|
|
308
321
|
return { type: 1 /* Number */, value: numStr, line: startLine, column: startColumn };
|
|
@@ -312,9 +325,12 @@ var Lexer = class _Lexer {
|
|
|
312
325
|
while (this.isAlphaNumeric(this.peek())) {
|
|
313
326
|
idStr += this.advance();
|
|
314
327
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
328
|
+
{
|
|
329
|
+
const nextCh = this.peek();
|
|
330
|
+
const charAfterNext = this.pos + 1 < this.input.length ? this.input[this.pos + 1] : "\0";
|
|
331
|
+
if (IDENTIFIER_TYPE_SUFFIXES.has(nextCh) && !((nextCh === "!" || nextCh === "^") && (this.isAlphaNumeric(charAfterNext) || charAfterNext === "_"))) {
|
|
332
|
+
idStr += this.advance();
|
|
333
|
+
}
|
|
318
334
|
}
|
|
319
335
|
const lowerId = idStr.toLowerCase();
|
|
320
336
|
const lowerBase = lowerId.replace(/[$%&#@]$/, "");
|
|
@@ -910,6 +926,9 @@ function applyEdits(source, edits) {
|
|
|
910
926
|
return lines.join("\n");
|
|
911
927
|
}
|
|
912
928
|
|
|
929
|
+
// ../../test-libs/version.ts
|
|
930
|
+
var VERSION = "0.1.1-alpha.1";
|
|
931
|
+
|
|
913
932
|
// ../../test-libs/vba-formatter.ts
|
|
914
933
|
var VBA_EXTENSIONS = /\.(bas|cls|frm)$/i;
|
|
915
934
|
function collectFiles(target) {
|
|
@@ -944,21 +963,25 @@ function buildDiff(original, formatted, filePath) {
|
|
|
944
963
|
}
|
|
945
964
|
return hasDiff ? diffLines.join("\n") : "";
|
|
946
965
|
}
|
|
947
|
-
function main() {
|
|
948
|
-
|
|
966
|
+
function main(args) {
|
|
967
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
968
|
+
console.log(VERSION);
|
|
969
|
+
process.exit(0);
|
|
970
|
+
}
|
|
949
971
|
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
950
|
-
console.log(`Usage: vba-
|
|
972
|
+
console.log(`Usage: vba-runner format <file-or-dir> [--check | --write] [options]
|
|
951
973
|
|
|
952
974
|
Options:
|
|
953
975
|
--check \u5DEE\u5206\u304C\u3042\u308C\u3070\u8868\u793A\u3057\u3066 exit code 1 \u3067\u7D42\u4E86
|
|
954
976
|
--write \u30D5\u30A1\u30A4\u30EB\u3092\u4E0A\u66F8\u304D\uFF08\u6307\u5B9A\u3057\u306A\u3044\u5834\u5408\u306F stdout \u306B\u51FA\u529B\uFF09
|
|
955
977
|
--indent-size=N \u30A4\u30F3\u30C7\u30F3\u30C8\u5E45\uFF08\u30C7\u30D5\u30A9\u30EB\u30C8: 4\uFF09
|
|
956
978
|
--no-keyword-case \u30AD\u30FC\u30EF\u30FC\u30C9\u306E\u5927\u6587\u5B57\u5316\u3092\u7121\u52B9\u5316
|
|
979
|
+
--version \u30D0\u30FC\u30B8\u30E7\u30F3\u3092\u8868\u793A
|
|
957
980
|
|
|
958
981
|
Example:
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
982
|
+
vba-runner format src/vba/Main.bas
|
|
983
|
+
vba-runner format src/vba/ --check
|
|
984
|
+
vba-runner format src/vba/Main.bas --write --indent-size=2`);
|
|
962
985
|
process.exit(0);
|
|
963
986
|
}
|
|
964
987
|
const target = args.find((a) => !a.startsWith("-"));
|
|
@@ -1009,4 +1032,8 @@ Example:
|
|
|
1009
1032
|
process.exit(1);
|
|
1010
1033
|
}
|
|
1011
1034
|
}
|
|
1012
|
-
main();
|
|
1035
|
+
if (process.argv[1]?.includes("vba-formatter")) main(process.argv.slice(2));
|
|
1036
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1037
|
+
0 && (module.exports = {
|
|
1038
|
+
main
|
|
1039
|
+
});
|