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.
@@ -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
- const peekChar = this.peek();
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
- const typeHints = ["$", "%", "&", "#", "@"];
336
- if (typeHints.includes(this.peek())) {
337
- idStr += this.advance();
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 additionally permitted as IDENTIFIERs in
589
- * expression context (parsePrimary) for practical VBA compatibility —
590
- * e.g. as method names (obj.Print, ws.Get) or file I/O targets.
591
- * These remain <reserved-identifier> per §3.3.5.2. */
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
- // <statement-keyword>
625
+ // enum < KeywordBase so also rejected by range check in parseProcedureDeclaration
603
626
  93 /* KeywordLock */,
604
- // <statement-keyword>
605
- 100 /* KeywordUnlock */
606
- // <statement-keyword>
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 !== 120 /* OperatorEquals */) {
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
- if (token.type === 0 /* Identifier */ && this.pos + 1 < this.tokens.length && this.tokens[this.pos + 1].type === 130 /* OperatorColon */) {
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.type !== 0 /* Identifier */ && labelToken.type !== 1 /* Number */) {
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.type !== 0 /* Identifier */) {
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 === 0 /* Identifier */) {
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.consume(0 /* Identifier */, "Expected procedure name after 'AddressOf'");
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.type !== 0 /* Identifier */ && typeToken.type !== 25 /* KeywordCollection */) {
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.type !== 0 /* Identifier */ && labelToken.type !== 1 /* Number */) {
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.type !== 0 /* Identifier */ && labelToken.type !== 1 /* Number */) {
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.consume(0 /* Identifier */, "Expected identifier after 'Event'");
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.consume(0 /* Identifier */, "Expected identifier after 'RaiseEvent'");
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.consume(0 /* Identifier */, "Expected interface name after 'Implements'");
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
- const args = process.argv.slice(2);
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: npx tsx test-libs/vba-analyzer.ts <file-or-dir> [options]");
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
- const peekChar = this.peek();
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
- const typeHints = ["$", "%", "&", "#", "@"];
316
- if (typeHints.includes(this.peek())) {
317
- idStr += this.advance();
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
- const args = process.argv.slice(2);
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-formatter <file-or-dir> [--check | --write] [options]
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
- npx tsx test-libs/vba-formatter.ts src/vba/Main.bas
960
- npx tsx test-libs/vba-formatter.ts src/vba/ --check
961
- npx tsx test-libs/vba-formatter.ts src/vba/Main.bas --write --indent-size=2`);
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
+ });