@g1cloud/api-gen 1.0.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.
Files changed (86) hide show
  1. package/.claude/settings.local.json +22 -0
  2. package/CLAUDE.md +63 -0
  3. package/README.md +379 -0
  4. package/dist/analyzer/controllerAnalyzer.d.ts +20 -0
  5. package/dist/analyzer/controllerAnalyzer.d.ts.map +1 -0
  6. package/dist/analyzer/controllerAnalyzer.js +101 -0
  7. package/dist/analyzer/controllerAnalyzer.js.map +1 -0
  8. package/dist/analyzer/parameterAnalyzer.d.ts +19 -0
  9. package/dist/analyzer/parameterAnalyzer.d.ts.map +1 -0
  10. package/dist/analyzer/parameterAnalyzer.js +207 -0
  11. package/dist/analyzer/parameterAnalyzer.js.map +1 -0
  12. package/dist/analyzer/responseAnalyzer.d.ts +12 -0
  13. package/dist/analyzer/responseAnalyzer.d.ts.map +1 -0
  14. package/dist/analyzer/responseAnalyzer.js +116 -0
  15. package/dist/analyzer/responseAnalyzer.js.map +1 -0
  16. package/dist/analyzer/schemaGenerator.d.ts +6 -0
  17. package/dist/analyzer/schemaGenerator.d.ts.map +1 -0
  18. package/dist/analyzer/schemaGenerator.js +347 -0
  19. package/dist/analyzer/schemaGenerator.js.map +1 -0
  20. package/dist/analyzer/securityAnalyzer.d.ts +6 -0
  21. package/dist/analyzer/securityAnalyzer.d.ts.map +1 -0
  22. package/dist/analyzer/securityAnalyzer.js +177 -0
  23. package/dist/analyzer/securityAnalyzer.js.map +1 -0
  24. package/dist/generator/openapiGenerator.d.ts +14 -0
  25. package/dist/generator/openapiGenerator.d.ts.map +1 -0
  26. package/dist/generator/openapiGenerator.js +340 -0
  27. package/dist/generator/openapiGenerator.js.map +1 -0
  28. package/dist/index.d.ts +3 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +218 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/lib.d.ts +61 -0
  33. package/dist/lib.d.ts.map +1 -0
  34. package/dist/lib.js +199 -0
  35. package/dist/lib.js.map +1 -0
  36. package/dist/mcp-server.d.ts +9 -0
  37. package/dist/mcp-server.d.ts.map +1 -0
  38. package/dist/mcp-server.js +257 -0
  39. package/dist/mcp-server.js.map +1 -0
  40. package/dist/mcp-server.mjs +45586 -0
  41. package/dist/parser/astAnalyzer.d.ts +87 -0
  42. package/dist/parser/astAnalyzer.d.ts.map +1 -0
  43. package/dist/parser/astAnalyzer.js +321 -0
  44. package/dist/parser/astAnalyzer.js.map +1 -0
  45. package/dist/parser/javaParser.d.ts +10 -0
  46. package/dist/parser/javaParser.d.ts.map +1 -0
  47. package/dist/parser/javaParser.js +805 -0
  48. package/dist/parser/javaParser.js.map +1 -0
  49. package/dist/types/index.d.ts +217 -0
  50. package/dist/types/index.d.ts.map +1 -0
  51. package/dist/types/index.js +3 -0
  52. package/dist/types/index.js.map +1 -0
  53. package/examples/CreateUserRequest.java +80 -0
  54. package/examples/DepartmentDTO.java +45 -0
  55. package/examples/Filter.java +39 -0
  56. package/examples/PaginatedList.java +71 -0
  57. package/examples/ProductController.java +136 -0
  58. package/examples/ProductDTO.java +129 -0
  59. package/examples/RoleDTO.java +47 -0
  60. package/examples/SearchParam.java +55 -0
  61. package/examples/Sort.java +70 -0
  62. package/examples/UpdateUserRequest.java +74 -0
  63. package/examples/UserController.java +98 -0
  64. package/examples/UserDTO.java +119 -0
  65. package/package.json +51 -0
  66. package/prompt/01_Initial.md +358 -0
  67. package/prompt/02_/354/266/224/352/260/200.md +31 -0
  68. package/src/analyzer/controllerAnalyzer.ts +125 -0
  69. package/src/analyzer/parameterAnalyzer.ts +259 -0
  70. package/src/analyzer/responseAnalyzer.ts +142 -0
  71. package/src/analyzer/schemaGenerator.ts +412 -0
  72. package/src/analyzer/securityAnalyzer.ts +200 -0
  73. package/src/generator/openapiGenerator.ts +378 -0
  74. package/src/index.ts +212 -0
  75. package/src/lib.ts +240 -0
  76. package/src/mcp-server.ts +310 -0
  77. package/src/parser/astAnalyzer.ts +373 -0
  78. package/src/parser/javaParser.ts +901 -0
  79. package/src/types/index.ts +238 -0
  80. package/test-boolean.yaml +607 -0
  81. package/test-filter.yaml +576 -0
  82. package/test-inner.ts +59 -0
  83. package/test-output.yaml +650 -0
  84. package/test-paginated.yaml +585 -0
  85. package/tsconfig.json +20 -0
  86. package/tsup.config.ts +30 -0
@@ -0,0 +1,901 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { parse, BaseJavaCstVisitorWithDefaults } from 'java-parser';
4
+ import { JavaClass, JavaMethod, JavaField, JavaParameter, JavaAnnotation, JavadocInfo } from '../types';
5
+
6
+ interface CstNode {
7
+ name: string;
8
+ children: Record<string, CstNode[]>;
9
+ image?: string;
10
+ location?: {
11
+ startLine: number;
12
+ startColumn: number;
13
+ };
14
+ }
15
+
16
+ interface ParsedJavadoc {
17
+ description: string;
18
+ info: JavadocInfo;
19
+ }
20
+
21
+ /**
22
+ * Extract Javadoc comments from source code
23
+ */
24
+ function extractJavadocs(content: string): Map<number, ParsedJavadoc> {
25
+ const javadocs = new Map<number, ParsedJavadoc>();
26
+ const javadocRegex = /\/\*\*[\s\S]*?\*\//g;
27
+ let match;
28
+
29
+ while ((match = javadocRegex.exec(content)) !== null) {
30
+ const javadocText = match[0];
31
+ // Count newlines to find the end line of the javadoc
32
+ const beforeMatch = content.substring(0, match.index + javadocText.length);
33
+ const endLine = beforeMatch.split('\n').length;
34
+
35
+ // Parse the javadoc content
36
+ const parsedJavadoc = parseJavadocContent(javadocText);
37
+ if (parsedJavadoc.description || parsedJavadoc.info.returns || Object.keys(parsedJavadoc.info.params).length > 0) {
38
+ javadocs.set(endLine, parsedJavadoc);
39
+ }
40
+ }
41
+
42
+ return javadocs;
43
+ }
44
+
45
+ /**
46
+ * Parse Javadoc content and extract the description, @param, and @return
47
+ */
48
+ function parseJavadocContent(javadoc: string): ParsedJavadoc {
49
+ // Remove /** and */
50
+ let content = javadoc.replace(/^\/\*\*/, '').replace(/\*\/$/, '');
51
+
52
+ // Split into lines and process each line
53
+ const lines = content.split('\n').map((line) => {
54
+ // Remove leading whitespace and asterisks
55
+ return line.replace(/^\s*\*?\s?/, '');
56
+ });
57
+
58
+ const descriptionLines: string[] = [];
59
+ const params: Record<string, string> = {};
60
+ let returns: string | undefined;
61
+
62
+ let currentTag: string | null = null;
63
+ let currentParamName: string | null = null;
64
+ let currentTagContent: string[] = [];
65
+
66
+ const flushCurrentTag = () => {
67
+ if (currentTag === 'param' && currentParamName) {
68
+ params[currentParamName] = currentTagContent.join(' ').trim();
69
+ } else if (currentTag === 'return' || currentTag === 'returns') {
70
+ returns = currentTagContent.join(' ').trim();
71
+ }
72
+ currentTag = null;
73
+ currentParamName = null;
74
+ currentTagContent = [];
75
+ };
76
+
77
+ for (const line of lines) {
78
+ if (line.startsWith('@param ')) {
79
+ flushCurrentTag();
80
+ currentTag = 'param';
81
+ // Extract param name and description
82
+ const paramMatch = line.match(/^@param\s+(\w+)\s*(.*)/);
83
+ if (paramMatch) {
84
+ currentParamName = paramMatch[1];
85
+ if (paramMatch[2]) {
86
+ currentTagContent.push(paramMatch[2]);
87
+ }
88
+ }
89
+ } else if (line.startsWith('@return ') || line.startsWith('@returns ')) {
90
+ flushCurrentTag();
91
+ currentTag = 'return';
92
+ const returnMatch = line.match(/^@returns?\s+(.*)/);
93
+ if (returnMatch && returnMatch[1]) {
94
+ currentTagContent.push(returnMatch[1]);
95
+ }
96
+ } else if (line.startsWith('@')) {
97
+ // Other tags like @throws, @see, etc. - flush current and ignore
98
+ flushCurrentTag();
99
+ } else if (currentTag) {
100
+ // Continuation of current tag
101
+ currentTagContent.push(line);
102
+ } else {
103
+ // Description line
104
+ descriptionLines.push(line);
105
+ }
106
+ }
107
+
108
+ // Flush any remaining tag
109
+ flushCurrentTag();
110
+
111
+ return {
112
+ description: descriptionLines.join('\n').trim(),
113
+ info: {
114
+ description: descriptionLines.join('\n').trim() || undefined,
115
+ params,
116
+ returns,
117
+ },
118
+ };
119
+ }
120
+
121
+ export class JavaFileParser {
122
+ private sourceDir: string;
123
+
124
+ constructor(sourceDir: string) {
125
+ this.sourceDir = sourceDir;
126
+ }
127
+
128
+ async parseAllJavaFiles(): Promise<Map<string, JavaClass>> {
129
+ const javaClasses = new Map<string, JavaClass>();
130
+ const javaFiles = this.findJavaFiles(this.sourceDir);
131
+
132
+ for (const filePath of javaFiles) {
133
+ try {
134
+ const classes = await this.parseJavaFile(filePath);
135
+ for (const javaClass of classes) {
136
+ javaClasses.set(javaClass.name, javaClass);
137
+ }
138
+ } catch (error) {
139
+ console.error(`Error parsing ${filePath}:`, error instanceof Error ? error.message : error);
140
+ }
141
+ }
142
+
143
+ return javaClasses;
144
+ }
145
+
146
+ private findJavaFiles(dir: string): string[] {
147
+ const files: string[] = [];
148
+
149
+ if (!fs.existsSync(dir)) {
150
+ return files;
151
+ }
152
+
153
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
154
+
155
+ for (const entry of entries) {
156
+ const fullPath = path.join(dir, entry.name);
157
+ if (entry.isDirectory()) {
158
+ files.push(...this.findJavaFiles(fullPath));
159
+ } else if (entry.isFile() && entry.name.endsWith('.java')) {
160
+ files.push(fullPath);
161
+ }
162
+ }
163
+
164
+ return files;
165
+ }
166
+
167
+ async parseJavaFile(filePath: string): Promise<JavaClass[]> {
168
+ const content = fs.readFileSync(filePath, 'utf-8');
169
+
170
+ try {
171
+ const cst = parse(content);
172
+ const javadocs = extractJavadocs(content);
173
+ const extractor = new JavaClassExtractor(filePath, content, javadocs);
174
+ extractor.visit(cst);
175
+ return extractor.getAllClasses();
176
+ } catch (error) {
177
+ console.error(`Failed to parse ${filePath}:`, error instanceof Error ? error.message : error);
178
+ return [];
179
+ }
180
+ }
181
+ }
182
+
183
+ // Represents the context while parsing a class (supports nested classes)
184
+ interface ClassContext {
185
+ className: string;
186
+ superClassName?: string;
187
+ classAnnotations: JavaAnnotation[];
188
+ classJavadoc?: string;
189
+ methods: JavaMethod[];
190
+ fields: JavaField[];
191
+ isEnum: boolean;
192
+ enumValues: string[];
193
+ }
194
+
195
+ class JavaClassExtractor extends BaseJavaCstVisitorWithDefaults {
196
+ private filePath: string;
197
+ private sourceContent: string;
198
+ private javadocs: Map<number, ParsedJavadoc>;
199
+ private _packageName: string = '';
200
+ private currentAnnotations: JavaAnnotation[] = [];
201
+
202
+ // Stack for handling nested classes
203
+ private classStack: ClassContext[] = [];
204
+ private allClasses: JavaClass[] = [];
205
+
206
+ constructor(filePath: string, sourceContent: string, javadocs: Map<number, ParsedJavadoc>) {
207
+ super();
208
+ this.filePath = filePath;
209
+ this.sourceContent = sourceContent;
210
+ this.javadocs = javadocs;
211
+ this.validateVisitor();
212
+ }
213
+
214
+ private get currentClass(): ClassContext | undefined {
215
+ return this.classStack[this.classStack.length - 1];
216
+ }
217
+
218
+ getJavaClass(): JavaClass | null {
219
+ // Return the first (top-level) class for backward compatibility
220
+ return this.allClasses.length > 0 ? this.allClasses[0] : null;
221
+ }
222
+
223
+ getAllClasses(): JavaClass[] {
224
+ return this.allClasses;
225
+ }
226
+
227
+ private finalizeClass(ctx: ClassContext): void {
228
+ this.allClasses.push({
229
+ name: ctx.className,
230
+ packageName: this._packageName,
231
+ annotations: ctx.classAnnotations,
232
+ methods: ctx.methods,
233
+ fields: ctx.fields,
234
+ filePath: this.filePath,
235
+ javadoc: ctx.classJavadoc,
236
+ superClass: ctx.superClassName,
237
+ isEnum: ctx.isEnum,
238
+ enumValues: ctx.isEnum ? ctx.enumValues : undefined,
239
+ });
240
+ }
241
+
242
+ packageDeclaration(ctx: any): void {
243
+ if (ctx.Identifier) {
244
+ this._packageName = ctx.Identifier.map((id: any) => id.image).join('.');
245
+ }
246
+ }
247
+
248
+ normalClassDeclaration(ctx: any): void {
249
+ // Create a new class context
250
+ const classContext: ClassContext = {
251
+ className: '',
252
+ classAnnotations: [...this.currentAnnotations],
253
+ methods: [],
254
+ fields: [],
255
+ isEnum: false,
256
+ enumValues: [],
257
+ };
258
+ this.currentAnnotations = [];
259
+
260
+ // Extract class name
261
+ if (ctx.typeIdentifier?.[0]?.children?.Identifier?.[0]) {
262
+ classContext.className = ctx.typeIdentifier[0].children.Identifier[0].image;
263
+
264
+ // Find javadoc for this class
265
+ const classToken = ctx.typeIdentifier[0].children.Identifier[0];
266
+ if (classToken.startLine) {
267
+ const parsedJavadoc = this.findJavadocForLine(classToken.startLine);
268
+ classContext.classJavadoc = parsedJavadoc?.description;
269
+ }
270
+ }
271
+
272
+ // Extract superclass name from classExtends
273
+ if (ctx.classExtends?.[0]) {
274
+ const classExtendsChildren = ctx.classExtends[0].children || ctx.classExtends[0];
275
+ if (classExtendsChildren.classType?.[0]) {
276
+ const classTypeChildren = classExtendsChildren.classType[0].children || classExtendsChildren.classType[0];
277
+ if (classTypeChildren.Identifier) {
278
+ // Join all identifiers for fully qualified names
279
+ classContext.superClassName = classTypeChildren.Identifier.map((id: any) => id.image).join('.');
280
+ }
281
+ }
282
+ }
283
+
284
+ // Push to stack and visit class body
285
+ this.classStack.push(classContext);
286
+
287
+ if (ctx.classBody) {
288
+ this.visit(ctx.classBody);
289
+ }
290
+
291
+ // Pop from stack and finalize the class
292
+ const completed = this.classStack.pop();
293
+ if (completed && completed.className) {
294
+ this.finalizeClass(completed);
295
+ }
296
+ }
297
+
298
+ enumDeclaration(ctx: any): void {
299
+ // Create a new enum context
300
+ const enumContext: ClassContext = {
301
+ className: '',
302
+ classAnnotations: [...this.currentAnnotations],
303
+ methods: [],
304
+ fields: [],
305
+ isEnum: true,
306
+ enumValues: [],
307
+ };
308
+ this.currentAnnotations = [];
309
+
310
+ // Get enum name
311
+ if (ctx.typeIdentifier?.[0]?.children?.Identifier?.[0]) {
312
+ enumContext.className = ctx.typeIdentifier[0].children.Identifier[0].image;
313
+
314
+ // Find javadoc for this enum
315
+ const enumToken = ctx.typeIdentifier[0].children.Identifier[0];
316
+ if (enumToken.startLine) {
317
+ const parsedJavadoc = this.findJavadocForLine(enumToken.startLine);
318
+ enumContext.classJavadoc = parsedJavadoc?.description;
319
+ }
320
+ }
321
+
322
+ // Extract enum constants
323
+ if (ctx.enumBody?.[0]) {
324
+ const enumBodyChildren = ctx.enumBody[0].children || ctx.enumBody[0];
325
+ if (enumBodyChildren.enumConstantList?.[0]) {
326
+ const constantListChildren = enumBodyChildren.enumConstantList[0].children || enumBodyChildren.enumConstantList[0];
327
+ if (constantListChildren.enumConstant) {
328
+ for (const enumConstant of constantListChildren.enumConstant) {
329
+ const constantChildren = enumConstant.children || enumConstant;
330
+ if (constantChildren.Identifier?.[0]) {
331
+ enumContext.enumValues.push(constantChildren.Identifier[0].image);
332
+ }
333
+ }
334
+ }
335
+ }
336
+ }
337
+
338
+ // Finalize the enum (enums don't have a body to visit for nested classes)
339
+ if (enumContext.className) {
340
+ this.finalizeClass(enumContext);
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Find javadoc that ends near the given line (within a few lines before)
346
+ */
347
+ private findJavadocForLine(targetLine: number): ParsedJavadoc | undefined {
348
+ // Look for javadoc that ends 1-10 lines before the target line
349
+ // (allowing for annotations between javadoc and declaration)
350
+ for (let offset = 1; offset <= 10; offset++) {
351
+ const javadoc = this.javadocs.get(targetLine - offset);
352
+ if (javadoc) {
353
+ return javadoc;
354
+ }
355
+ }
356
+ return undefined;
357
+ }
358
+
359
+ classModifier(ctx: any): void {
360
+ if (ctx.annotation) {
361
+ for (const annotationCtx of ctx.annotation) {
362
+ const annotation = this.extractAnnotation(annotationCtx);
363
+ if (annotation) {
364
+ this.currentAnnotations.push(annotation);
365
+ }
366
+ }
367
+ }
368
+ }
369
+
370
+ methodDeclaration(ctx: any): void {
371
+ // Extract annotations directly from methodModifier nodes
372
+ const methodAnnotations: JavaAnnotation[] = [];
373
+ let methodStartLine: number | undefined;
374
+
375
+ if (ctx.methodModifier) {
376
+ for (const modifierCtx of ctx.methodModifier) {
377
+ const modChildren = modifierCtx.children || modifierCtx;
378
+ if (modChildren.annotation) {
379
+ for (const annotationCtx of modChildren.annotation) {
380
+ const annotation = this.extractAnnotation(annotationCtx);
381
+ if (annotation) {
382
+ methodAnnotations.push(annotation);
383
+ }
384
+ // Track the earliest line of the method declaration
385
+ const annChildren = annotationCtx.children || annotationCtx;
386
+ if (annChildren.At?.[0]?.startLine && (!methodStartLine || annChildren.At[0].startLine < methodStartLine)) {
387
+ methodStartLine = annChildren.At[0].startLine;
388
+ }
389
+ }
390
+ }
391
+ }
392
+ }
393
+
394
+ const methodHeader = ctx.methodHeader?.[0];
395
+ if (!methodHeader) return;
396
+
397
+ const methodName = this.extractMethodName(methodHeader);
398
+ const returnType = this.extractReturnType(methodHeader);
399
+ const parameters = this.extractParameters(methodHeader);
400
+
401
+ // Find javadoc for this method
402
+ let methodJavadoc: string | undefined;
403
+ let methodJavadocInfo: JavadocInfo | undefined;
404
+ if (methodStartLine) {
405
+ const parsedJavadoc = this.findJavadocForLine(methodStartLine);
406
+ methodJavadoc = parsedJavadoc?.description;
407
+ methodJavadocInfo = parsedJavadoc?.info;
408
+ }
409
+
410
+ if (methodName && this.currentClass) {
411
+ this.currentClass.methods.push({
412
+ name: methodName,
413
+ returnType: returnType.type,
414
+ returnGenericType: returnType.genericType,
415
+ parameters,
416
+ annotations: methodAnnotations,
417
+ javadoc: methodJavadoc,
418
+ javadocInfo: methodJavadocInfo,
419
+ });
420
+ }
421
+ }
422
+
423
+ fieldDeclaration(ctx: any): void {
424
+ // Extract annotations and check for static modifier from fieldModifier nodes
425
+ const fieldAnnotations: JavaAnnotation[] = [];
426
+ let isStatic = false;
427
+ let fieldStartLine: number | undefined;
428
+
429
+ if (ctx.fieldModifier) {
430
+ for (const modifier of ctx.fieldModifier) {
431
+ const modChildren = modifier.children || modifier;
432
+
433
+ // Check for static modifier
434
+ if (modChildren.Static) {
435
+ isStatic = true;
436
+ }
437
+
438
+ // Extract annotations and track start line
439
+ if (modChildren.annotation) {
440
+ for (const annotationCtx of modChildren.annotation) {
441
+ const annotation = this.extractAnnotation(annotationCtx);
442
+ if (annotation) {
443
+ fieldAnnotations.push(annotation);
444
+ }
445
+ // Track the earliest line of the field declaration
446
+ const annChildren = annotationCtx.children || annotationCtx;
447
+ if (annChildren.At?.[0]?.startLine && (!fieldStartLine || annChildren.At[0].startLine < fieldStartLine)) {
448
+ fieldStartLine = annChildren.At[0].startLine;
449
+ }
450
+ }
451
+ }
452
+ }
453
+ }
454
+
455
+ // Skip static fields
456
+ if (isStatic) return;
457
+
458
+ const unannType = ctx.unannType?.[0];
459
+ const variableDeclaratorList = ctx.variableDeclaratorList?.[0];
460
+
461
+ if (!unannType || !variableDeclaratorList) return;
462
+
463
+ // If no annotation found, try to get start line from type
464
+ if (!fieldStartLine && unannType) {
465
+ fieldStartLine = this.getStartLineFromNode(unannType);
466
+ }
467
+
468
+ const typeInfo = this.extractType(unannType);
469
+ const fieldNames = this.extractFieldNames(variableDeclaratorList);
470
+
471
+ // Find javadoc for this field
472
+ let fieldJavadoc: string | undefined;
473
+ if (fieldStartLine) {
474
+ const parsedJavadoc = this.findJavadocForLine(fieldStartLine);
475
+ fieldJavadoc = parsedJavadoc?.description;
476
+ }
477
+
478
+ if (this.currentClass) {
479
+ for (const fieldName of fieldNames) {
480
+ this.currentClass.fields.push({
481
+ name: fieldName,
482
+ type: typeInfo.type,
483
+ genericType: typeInfo.genericType,
484
+ annotations: fieldAnnotations,
485
+ accessModifier: 'private', // Default, could be extracted from modifiers
486
+ javadoc: fieldJavadoc,
487
+ });
488
+ }
489
+ }
490
+ }
491
+
492
+ /**
493
+ * Get the start line from a CST node by recursively searching for tokens
494
+ */
495
+ private getStartLineFromNode(node: any): number | undefined {
496
+ if (!node) return undefined;
497
+
498
+ const children = node.children || node;
499
+
500
+ // Check if this is a token with startLine
501
+ if (children.startLine) {
502
+ return children.startLine;
503
+ }
504
+
505
+ // Recursively search children
506
+ for (const key of Object.keys(children)) {
507
+ const child = children[key];
508
+ if (Array.isArray(child)) {
509
+ for (const item of child) {
510
+ if (item?.startLine) {
511
+ return item.startLine;
512
+ }
513
+ const result = this.getStartLineFromNode(item);
514
+ if (result) return result;
515
+ }
516
+ } else if (child?.startLine) {
517
+ return child.startLine;
518
+ }
519
+ }
520
+
521
+ return undefined;
522
+ }
523
+
524
+ private extractAnnotation(ctx: any): JavaAnnotation | null {
525
+ const children = ctx.children || ctx;
526
+
527
+ // Get annotation name
528
+ let name = '';
529
+ if (children.typeName?.[0]) {
530
+ name = this.extractTypeName(children.typeName[0]);
531
+ } else if (children.Identifier) {
532
+ name = children.Identifier.map((id: any) => id.image).join('.');
533
+ }
534
+
535
+ if (!name) return null;
536
+
537
+ // Remove @ prefix if present
538
+ name = name.replace(/^@/, '');
539
+
540
+ // Extract annotation values
541
+ const values: Record<string, string | string[] | boolean> = {};
542
+
543
+ if (children.elementValuePairList?.[0]) {
544
+ this.extractElementValuePairs(children.elementValuePairList[0], values);
545
+ } else if (children.elementValue?.[0]) {
546
+ // Single value annotation like @GetMapping("/path")
547
+ values['value'] = this.extractElementValue(children.elementValue[0]);
548
+ }
549
+
550
+ return { name, values };
551
+ }
552
+
553
+ private extractElementValuePairs(ctx: any, values: Record<string, string | string[] | boolean>): void {
554
+ const children = ctx.children || ctx;
555
+ const pairs = children.elementValuePair || [];
556
+
557
+ for (const pair of pairs) {
558
+ const pairChildren = pair.children || pair;
559
+ const key = pairChildren.Identifier?.[0]?.image || 'value';
560
+ const value = this.extractElementValue(pairChildren.elementValue?.[0]);
561
+ values[key] = value;
562
+ }
563
+ }
564
+
565
+ private extractElementValue(ctx: any): string | string[] {
566
+ if (!ctx) return '';
567
+
568
+ const children = ctx.children || ctx;
569
+
570
+ // Array initializer
571
+ if (children.elementValueArrayInitializer?.[0]) {
572
+ const arrayInit = children.elementValueArrayInitializer[0];
573
+ const arrayChildren = arrayInit.children || arrayInit;
574
+ const elementValues = arrayChildren.elementValue || [];
575
+ return elementValues.map((ev: any) => this.extractElementValue(ev)).flat();
576
+ }
577
+
578
+ // Conditional expression (includes literals and identifiers)
579
+ if (children.conditionalExpression?.[0]) {
580
+ return this.extractExpressionValue(children.conditionalExpression[0]);
581
+ }
582
+
583
+ // Annotation
584
+ if (children.annotation?.[0]) {
585
+ return this.extractAnnotationAsString(children.annotation[0]);
586
+ }
587
+
588
+ return '';
589
+ }
590
+
591
+ private extractExpressionValue(ctx: any): string {
592
+ const children = ctx.children || ctx;
593
+
594
+ // Handle string literals
595
+ if (children.StringLiteral?.[0]) {
596
+ return children.StringLiteral[0].image.replace(/^"|"$/g, '');
597
+ }
598
+
599
+ // Handle boolean literals
600
+ if (children.True?.[0]) return 'true';
601
+ if (children.False?.[0]) return 'false';
602
+
603
+ // Handle numeric literals
604
+ if (children.IntegerLiteral?.[0]) return children.IntegerLiteral[0].image;
605
+ if (children.FloatingPointLiteral?.[0]) return children.FloatingPointLiteral[0].image;
606
+
607
+ // Handle identifiers (enum values, etc.)
608
+ if (children.Identifier?.[0]) return children.Identifier[0].image;
609
+
610
+ // Recursively search for values in nested structures
611
+ for (const key of Object.keys(children)) {
612
+ if (Array.isArray(children[key]) && children[key].length > 0) {
613
+ const result = this.extractExpressionValue(children[key][0]);
614
+ if (result) return result;
615
+ }
616
+ }
617
+
618
+ return '';
619
+ }
620
+
621
+ private extractAnnotationAsString(ctx: any): string {
622
+ const children = ctx.children || ctx;
623
+ let name = '';
624
+ if (children.typeName?.[0]) {
625
+ name = this.extractTypeName(children.typeName[0]);
626
+ }
627
+ return `@${name}`;
628
+ }
629
+
630
+ private extractTypeName(ctx: any): string {
631
+ const children = ctx.children || ctx;
632
+ if (children.Identifier) {
633
+ return children.Identifier.map((id: any) => id.image).join('.');
634
+ }
635
+ return '';
636
+ }
637
+
638
+ private extractMethodName(methodHeader: any): string {
639
+ const children = methodHeader.children || methodHeader;
640
+ const declarator = children.methodDeclarator?.[0];
641
+ if (declarator) {
642
+ const declChildren = declarator.children || declarator;
643
+ return declChildren.Identifier?.[0]?.image || '';
644
+ }
645
+ return '';
646
+ }
647
+
648
+ private extractReturnType(methodHeader: any): { type: string; genericType?: string } {
649
+ const children = methodHeader.children || methodHeader;
650
+ const result = children.result?.[0];
651
+
652
+ if (!result) return { type: 'void' };
653
+
654
+ const resultChildren = result.children || result;
655
+
656
+ if (resultChildren.Void) {
657
+ return { type: 'void' };
658
+ }
659
+
660
+ if (resultChildren.unannType?.[0]) {
661
+ return this.extractType(resultChildren.unannType[0]);
662
+ }
663
+
664
+ return { type: 'Object' };
665
+ }
666
+
667
+ private extractType(typeCtx: any): { type: string; genericType?: string } {
668
+ const children = typeCtx.children || typeCtx;
669
+
670
+ // Handle primitive types with optional dims suffix (e.g., boolean, int, etc.)
671
+ if (children.unannPrimitiveTypeWithOptionalDimsSuffix?.[0]) {
672
+ const primitiveWithDims = children.unannPrimitiveTypeWithOptionalDimsSuffix[0].children || children.unannPrimitiveTypeWithOptionalDimsSuffix[0];
673
+ if (primitiveWithDims.unannPrimitiveType?.[0]) {
674
+ const primitiveChildren = primitiveWithDims.unannPrimitiveType[0].children || primitiveWithDims.unannPrimitiveType[0];
675
+ return this.extractPrimitiveType(primitiveChildren);
676
+ }
677
+ }
678
+
679
+ // Handle primitive types directly (fallback for older parser versions)
680
+ if (children.unannPrimitiveType?.[0]) {
681
+ const primitiveChildren = children.unannPrimitiveType[0].children || children.unannPrimitiveType[0];
682
+ return this.extractPrimitiveType(primitiveChildren);
683
+ }
684
+
685
+ // Handle reference types
686
+ if (children.unannReferenceType?.[0]) {
687
+ return this.extractReferenceType(children.unannReferenceType[0]);
688
+ }
689
+
690
+ return { type: 'Object' };
691
+ }
692
+
693
+ private extractPrimitiveType(primitiveChildren: any): { type: string; genericType?: string } {
694
+ if (primitiveChildren.numericType?.[0]) {
695
+ const numericChildren = primitiveChildren.numericType[0].children || primitiveChildren.numericType[0];
696
+ if (numericChildren.integralType?.[0]) {
697
+ const integralChildren = numericChildren.integralType[0].children || numericChildren.integralType[0];
698
+ for (const key of ['Int', 'Long', 'Short', 'Byte', 'Char']) {
699
+ if (integralChildren[key]) return { type: key.toLowerCase() };
700
+ }
701
+ }
702
+ if (numericChildren.floatingPointType?.[0]) {
703
+ const floatChildren = numericChildren.floatingPointType[0].children || numericChildren.floatingPointType[0];
704
+ if (floatChildren.Float) return { type: 'float' };
705
+ if (floatChildren.Double) return { type: 'double' };
706
+ }
707
+ }
708
+ if (primitiveChildren.Boolean) return { type: 'boolean' };
709
+ return { type: 'Object' };
710
+ }
711
+
712
+ private extractReferenceType(refTypeCtx: any): { type: string; genericType?: string } {
713
+ const children = refTypeCtx.children || refTypeCtx;
714
+
715
+ // Handle class or interface type
716
+ if (children.unannClassOrInterfaceType?.[0]) {
717
+ return this.extractClassOrInterfaceType(children.unannClassOrInterfaceType[0]);
718
+ }
719
+
720
+ // Handle array type
721
+ if (children.unannArrayType?.[0]) {
722
+ const arrayChildren = children.unannArrayType[0].children || children.unannArrayType[0];
723
+ if (arrayChildren.unannClassOrInterfaceType?.[0]) {
724
+ const innerType = this.extractClassOrInterfaceType(arrayChildren.unannClassOrInterfaceType[0]);
725
+ return { type: `${innerType.type}[]`, genericType: innerType.genericType };
726
+ }
727
+ }
728
+
729
+ return { type: 'Object' };
730
+ }
731
+
732
+ private extractClassOrInterfaceType(ctx: any): { type: string; genericType?: string } {
733
+ const children = ctx.children || ctx;
734
+ let type = '';
735
+ let genericType: string | undefined;
736
+
737
+ // Handle the class type
738
+ if (children.unannClassType?.[0]) {
739
+ const classTypeChildren = children.unannClassType[0].children || children.unannClassType[0];
740
+
741
+ // Get the class name from annotation chain or directly
742
+ if (classTypeChildren.Identifier) {
743
+ type = classTypeChildren.Identifier.map((id: any) => id.image).join('.');
744
+ }
745
+
746
+ // Check for generic type arguments
747
+ if (classTypeChildren.typeArguments?.[0]) {
748
+ genericType = this.extractTypeArguments(classTypeChildren.typeArguments[0]);
749
+ }
750
+ }
751
+
752
+ return { type: type || 'Object', genericType };
753
+ }
754
+
755
+ private extractTypeArguments(ctx: any): string {
756
+ const children = ctx.children || ctx;
757
+ const typeArgumentList = children.typeArgumentList?.[0];
758
+
759
+ if (!typeArgumentList) return '';
760
+
761
+ const typeArgChildren = typeArgumentList.children || typeArgumentList;
762
+ const typeArguments = typeArgChildren.typeArgument || [];
763
+
764
+ const types = typeArguments.map((arg: any) => {
765
+ const argChildren = arg.children || arg;
766
+ if (argChildren.referenceType?.[0]) {
767
+ const refType = this.extractReferenceTypeFromGeneric(argChildren.referenceType[0]);
768
+ return refType;
769
+ }
770
+ if (argChildren.wildcard?.[0]) {
771
+ return '?';
772
+ }
773
+ return 'Object';
774
+ });
775
+
776
+ return types.join(', ');
777
+ }
778
+
779
+ private extractReferenceTypeFromGeneric(refTypeCtx: any): string {
780
+ const children = refTypeCtx.children || refTypeCtx;
781
+
782
+ if (children.classOrInterfaceType?.[0]) {
783
+ const classTypeChildren = children.classOrInterfaceType[0].children || children.classOrInterfaceType[0];
784
+ if (classTypeChildren.classType?.[0]) {
785
+ const innerClassChildren = classTypeChildren.classType[0].children || classTypeChildren.classType[0];
786
+ if (innerClassChildren.Identifier) {
787
+ let typeName = innerClassChildren.Identifier.map((id: any) => id.image).join('.');
788
+
789
+ // Handle nested generic types
790
+ if (innerClassChildren.typeArguments?.[0]) {
791
+ const nestedGenerics = this.extractTypeArguments(innerClassChildren.typeArguments[0]);
792
+ if (nestedGenerics) {
793
+ typeName += `<${nestedGenerics}>`;
794
+ }
795
+ }
796
+
797
+ return typeName;
798
+ }
799
+ }
800
+ }
801
+
802
+ return 'Object';
803
+ }
804
+
805
+ private extractParameters(methodHeader: any): JavaParameter[] {
806
+ const children = methodHeader.children || methodHeader;
807
+ const declarator = children.methodDeclarator?.[0];
808
+ if (!declarator) return [];
809
+
810
+ const declChildren = declarator.children || declarator;
811
+ const formalParameterList = declChildren.formalParameterList?.[0];
812
+ if (!formalParameterList) return [];
813
+
814
+ const formalChildren = formalParameterList.children || formalParameterList;
815
+ const parameters: JavaParameter[] = [];
816
+
817
+ // Handle regular formal parameters
818
+ if (formalChildren.formalParameter) {
819
+ for (const param of formalChildren.formalParameter) {
820
+ const parameter = this.extractFormalParameter(param);
821
+ if (parameter) {
822
+ parameters.push(parameter);
823
+ }
824
+ }
825
+ }
826
+
827
+ return parameters;
828
+ }
829
+
830
+ private extractFormalParameter(paramCtx: any): JavaParameter | null {
831
+ let children = paramCtx.children || paramCtx;
832
+
833
+ // Handle variableParaRegularParameter wrapper
834
+ if (children.variableParaRegularParameter?.[0]) {
835
+ children = children.variableParaRegularParameter[0].children || children.variableParaRegularParameter[0];
836
+ }
837
+
838
+ // Extract annotations from variableModifier
839
+ const annotations: JavaAnnotation[] = [];
840
+ if (children.variableModifier) {
841
+ for (const modifier of children.variableModifier) {
842
+ const modChildren = modifier.children || modifier;
843
+ if (modChildren.annotation?.[0]) {
844
+ const annotation = this.extractAnnotation(modChildren.annotation[0]);
845
+ if (annotation) {
846
+ annotations.push(annotation);
847
+ }
848
+ }
849
+ }
850
+ }
851
+
852
+ // Extract type
853
+ let type = 'Object';
854
+ let genericType: string | undefined;
855
+ if (children.unannType?.[0]) {
856
+ const typeInfo = this.extractType(children.unannType[0]);
857
+ type = typeInfo.type;
858
+ genericType = typeInfo.genericType;
859
+ }
860
+
861
+ // Extract parameter name
862
+ let name = '';
863
+ if (children.variableDeclaratorId?.[0]) {
864
+ const varIdChildren = children.variableDeclaratorId[0].children || children.variableDeclaratorId[0];
865
+ name = varIdChildren.Identifier?.[0]?.image || '';
866
+ }
867
+
868
+ if (!name) return null;
869
+
870
+ return {
871
+ name,
872
+ type,
873
+ genericType,
874
+ annotations,
875
+ };
876
+ }
877
+
878
+ private extractFieldNames(variableDeclaratorList: any): string[] {
879
+ const children = variableDeclaratorList.children || variableDeclaratorList;
880
+ const declarators = children.variableDeclarator || [];
881
+ const names: string[] = [];
882
+
883
+ for (const declarator of declarators) {
884
+ const declChildren = declarator.children || declarator;
885
+ if (declChildren.variableDeclaratorId?.[0]) {
886
+ const varIdChildren = declChildren.variableDeclaratorId[0].children || declChildren.variableDeclaratorId[0];
887
+ const name = varIdChildren.Identifier?.[0]?.image;
888
+ if (name) {
889
+ names.push(name);
890
+ }
891
+ }
892
+ }
893
+
894
+ return names;
895
+ }
896
+ }
897
+
898
+ export function parseJavaSource(sourceDir: string): Promise<Map<string, JavaClass>> {
899
+ const parser = new JavaFileParser(sourceDir);
900
+ return parser.parseAllJavaFiles();
901
+ }