@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.
- package/.claude/settings.local.json +22 -0
- package/CLAUDE.md +63 -0
- package/README.md +379 -0
- package/dist/analyzer/controllerAnalyzer.d.ts +20 -0
- package/dist/analyzer/controllerAnalyzer.d.ts.map +1 -0
- package/dist/analyzer/controllerAnalyzer.js +101 -0
- package/dist/analyzer/controllerAnalyzer.js.map +1 -0
- package/dist/analyzer/parameterAnalyzer.d.ts +19 -0
- package/dist/analyzer/parameterAnalyzer.d.ts.map +1 -0
- package/dist/analyzer/parameterAnalyzer.js +207 -0
- package/dist/analyzer/parameterAnalyzer.js.map +1 -0
- package/dist/analyzer/responseAnalyzer.d.ts +12 -0
- package/dist/analyzer/responseAnalyzer.d.ts.map +1 -0
- package/dist/analyzer/responseAnalyzer.js +116 -0
- package/dist/analyzer/responseAnalyzer.js.map +1 -0
- package/dist/analyzer/schemaGenerator.d.ts +6 -0
- package/dist/analyzer/schemaGenerator.d.ts.map +1 -0
- package/dist/analyzer/schemaGenerator.js +347 -0
- package/dist/analyzer/schemaGenerator.js.map +1 -0
- package/dist/analyzer/securityAnalyzer.d.ts +6 -0
- package/dist/analyzer/securityAnalyzer.d.ts.map +1 -0
- package/dist/analyzer/securityAnalyzer.js +177 -0
- package/dist/analyzer/securityAnalyzer.js.map +1 -0
- package/dist/generator/openapiGenerator.d.ts +14 -0
- package/dist/generator/openapiGenerator.d.ts.map +1 -0
- package/dist/generator/openapiGenerator.js +340 -0
- package/dist/generator/openapiGenerator.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +218 -0
- package/dist/index.js.map +1 -0
- package/dist/lib.d.ts +61 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +199 -0
- package/dist/lib.js.map +1 -0
- package/dist/mcp-server.d.ts +9 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +257 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/mcp-server.mjs +45586 -0
- package/dist/parser/astAnalyzer.d.ts +87 -0
- package/dist/parser/astAnalyzer.d.ts.map +1 -0
- package/dist/parser/astAnalyzer.js +321 -0
- package/dist/parser/astAnalyzer.js.map +1 -0
- package/dist/parser/javaParser.d.ts +10 -0
- package/dist/parser/javaParser.d.ts.map +1 -0
- package/dist/parser/javaParser.js +805 -0
- package/dist/parser/javaParser.js.map +1 -0
- package/dist/types/index.d.ts +217 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/examples/CreateUserRequest.java +80 -0
- package/examples/DepartmentDTO.java +45 -0
- package/examples/Filter.java +39 -0
- package/examples/PaginatedList.java +71 -0
- package/examples/ProductController.java +136 -0
- package/examples/ProductDTO.java +129 -0
- package/examples/RoleDTO.java +47 -0
- package/examples/SearchParam.java +55 -0
- package/examples/Sort.java +70 -0
- package/examples/UpdateUserRequest.java +74 -0
- package/examples/UserController.java +98 -0
- package/examples/UserDTO.java +119 -0
- package/package.json +51 -0
- package/prompt/01_Initial.md +358 -0
- package/prompt/02_/354/266/224/352/260/200.md +31 -0
- package/src/analyzer/controllerAnalyzer.ts +125 -0
- package/src/analyzer/parameterAnalyzer.ts +259 -0
- package/src/analyzer/responseAnalyzer.ts +142 -0
- package/src/analyzer/schemaGenerator.ts +412 -0
- package/src/analyzer/securityAnalyzer.ts +200 -0
- package/src/generator/openapiGenerator.ts +378 -0
- package/src/index.ts +212 -0
- package/src/lib.ts +240 -0
- package/src/mcp-server.ts +310 -0
- package/src/parser/astAnalyzer.ts +373 -0
- package/src/parser/javaParser.ts +901 -0
- package/src/types/index.ts +238 -0
- package/test-boolean.yaml +607 -0
- package/test-filter.yaml +576 -0
- package/test-inner.ts +59 -0
- package/test-output.yaml +650 -0
- package/test-paginated.yaml +585 -0
- package/tsconfig.json +20 -0
- 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
|
+
}
|