activo 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -3
- package/dist/core/commands.d.ts +11 -0
- package/dist/core/commands.d.ts.map +1 -0
- package/dist/core/commands.js +90 -0
- package/dist/core/commands.js.map +1 -0
- package/dist/core/llm/ollama.d.ts +2 -0
- package/dist/core/llm/ollama.d.ts.map +1 -1
- package/dist/core/llm/ollama.js +26 -0
- package/dist/core/llm/ollama.js.map +1 -1
- package/dist/core/tools/ast.d.ts +81 -0
- package/dist/core/tools/ast.d.ts.map +1 -0
- package/dist/core/tools/ast.js +700 -0
- package/dist/core/tools/ast.js.map +1 -0
- package/dist/core/tools/cache.d.ts +19 -0
- package/dist/core/tools/cache.d.ts.map +1 -0
- package/dist/core/tools/cache.js +497 -0
- package/dist/core/tools/cache.js.map +1 -0
- package/dist/core/tools/cssAnalysis.d.ts +3 -0
- package/dist/core/tools/cssAnalysis.d.ts.map +1 -0
- package/dist/core/tools/cssAnalysis.js +270 -0
- package/dist/core/tools/cssAnalysis.js.map +1 -0
- package/dist/core/tools/embeddings.d.ts +8 -0
- package/dist/core/tools/embeddings.d.ts.map +1 -0
- package/dist/core/tools/embeddings.js +631 -0
- package/dist/core/tools/embeddings.js.map +1 -0
- package/dist/core/tools/frontendAst.d.ts +6 -0
- package/dist/core/tools/frontendAst.d.ts.map +1 -0
- package/dist/core/tools/frontendAst.js +680 -0
- package/dist/core/tools/frontendAst.js.map +1 -0
- package/dist/core/tools/htmlAnalysis.d.ts +3 -0
- package/dist/core/tools/htmlAnalysis.d.ts.map +1 -0
- package/dist/core/tools/htmlAnalysis.js +398 -0
- package/dist/core/tools/htmlAnalysis.js.map +1 -0
- package/dist/core/tools/index.d.ts +10 -0
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +21 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/javaAst.d.ts +6 -0
- package/dist/core/tools/javaAst.d.ts.map +1 -0
- package/dist/core/tools/javaAst.js +678 -0
- package/dist/core/tools/javaAst.js.map +1 -0
- package/dist/core/tools/memory.d.ts +11 -0
- package/dist/core/tools/memory.d.ts.map +1 -0
- package/dist/core/tools/memory.js +551 -0
- package/dist/core/tools/memory.js.map +1 -0
- package/dist/core/tools/mybatisAnalysis.d.ts +3 -0
- package/dist/core/tools/mybatisAnalysis.d.ts.map +1 -0
- package/dist/core/tools/mybatisAnalysis.js +251 -0
- package/dist/core/tools/mybatisAnalysis.js.map +1 -0
- package/dist/core/tools/sqlAnalysis.d.ts +3 -0
- package/dist/core/tools/sqlAnalysis.d.ts.map +1 -0
- package/dist/core/tools/sqlAnalysis.js +250 -0
- package/dist/core/tools/sqlAnalysis.js.map +1 -0
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +31 -2
- package/dist/ui/App.js.map +1 -1
- package/package.json +2 -1
- package/src/core/commands.ts +118 -0
- package/src/core/llm/ollama.ts +30 -0
- package/src/core/tools/ast.ts +826 -0
- package/src/core/tools/cache.ts +570 -0
- package/src/core/tools/cssAnalysis.ts +324 -0
- package/src/core/tools/embeddings.ts +746 -0
- package/src/core/tools/frontendAst.ts +802 -0
- package/src/core/tools/htmlAnalysis.ts +466 -0
- package/src/core/tools/index.ts +21 -1
- package/src/core/tools/javaAst.ts +812 -0
- package/src/core/tools/memory.ts +655 -0
- package/src/core/tools/mybatisAnalysis.ts +322 -0
- package/src/core/tools/sqlAnalysis.ts +298 -0
- package/src/ui/App.tsx +38 -2
- package/FINAL_SIMPLIFIED_SPEC.md +0 -456
- package/TODO.md +0 -193
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { parse, createVisitor } from "java-ast";
|
|
4
|
+
// Calculate cyclomatic complexity from method body text
|
|
5
|
+
function calculateComplexity(methodText) {
|
|
6
|
+
let complexity = 1;
|
|
7
|
+
// Count decision points
|
|
8
|
+
const patterns = [
|
|
9
|
+
/\bif\s*\(/g,
|
|
10
|
+
/\belse\s+if\s*\(/g,
|
|
11
|
+
/\bfor\s*\(/g,
|
|
12
|
+
/\bwhile\s*\(/g,
|
|
13
|
+
/\bcase\s+/g,
|
|
14
|
+
/\bcatch\s*\(/g,
|
|
15
|
+
/\b\?\s*[^:]/g, // ternary operator
|
|
16
|
+
/\&\&/g,
|
|
17
|
+
/\|\|/g,
|
|
18
|
+
];
|
|
19
|
+
for (const pattern of patterns) {
|
|
20
|
+
const matches = methodText.match(pattern);
|
|
21
|
+
if (matches) {
|
|
22
|
+
complexity += matches.length;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return complexity;
|
|
26
|
+
}
|
|
27
|
+
// Analyze Java file
|
|
28
|
+
function analyzeJavaFile(content, filepath) {
|
|
29
|
+
const analysis = {
|
|
30
|
+
filepath,
|
|
31
|
+
package: "",
|
|
32
|
+
imports: [],
|
|
33
|
+
classes: [],
|
|
34
|
+
complexity: { total: 0, average: 0, highest: { name: "", value: 0 } },
|
|
35
|
+
};
|
|
36
|
+
try {
|
|
37
|
+
const tree = parse(content);
|
|
38
|
+
const lines = content.split("\n");
|
|
39
|
+
// Get line number from context
|
|
40
|
+
const getLine = (ctx) => {
|
|
41
|
+
if (ctx && ctx.start) {
|
|
42
|
+
return ctx.start.line;
|
|
43
|
+
}
|
|
44
|
+
return 0;
|
|
45
|
+
};
|
|
46
|
+
// Get text from context
|
|
47
|
+
const getText = (ctx) => {
|
|
48
|
+
if (ctx && ctx.start && ctx.stop) {
|
|
49
|
+
const startIdx = ctx.start.startIndex;
|
|
50
|
+
const stopIdx = ctx.stop.stopIndex;
|
|
51
|
+
return content.substring(startIdx, stopIdx + 1);
|
|
52
|
+
}
|
|
53
|
+
return ctx?.text || "";
|
|
54
|
+
};
|
|
55
|
+
// Get modifiers
|
|
56
|
+
const getModifiers = (modifierCtxs) => {
|
|
57
|
+
if (!modifierCtxs)
|
|
58
|
+
return [];
|
|
59
|
+
return modifierCtxs.map((m) => m.text).filter((m) => m);
|
|
60
|
+
};
|
|
61
|
+
// Get annotations
|
|
62
|
+
const getAnnotations = (modifierCtxs) => {
|
|
63
|
+
if (!modifierCtxs)
|
|
64
|
+
return [];
|
|
65
|
+
return modifierCtxs
|
|
66
|
+
.filter((m) => m.annotation)
|
|
67
|
+
.map((m) => m.annotation()?.qualifiedName()?.text || m.text)
|
|
68
|
+
.filter((a) => a);
|
|
69
|
+
};
|
|
70
|
+
// Parse using visitor pattern
|
|
71
|
+
const visitor = createVisitor({
|
|
72
|
+
defaultResult: () => null,
|
|
73
|
+
aggregateResult: (a, b) => b || a,
|
|
74
|
+
visitPackageDeclaration: (ctx) => {
|
|
75
|
+
analysis.package = ctx.qualifiedName()?.text || "";
|
|
76
|
+
return null;
|
|
77
|
+
},
|
|
78
|
+
visitImportDeclaration: (ctx) => {
|
|
79
|
+
const importText = ctx.qualifiedName()?.text || "";
|
|
80
|
+
if (importText) {
|
|
81
|
+
const isStatic = ctx.STATIC() ? "static " : "";
|
|
82
|
+
const isWildcard = ctx.MUL() ? ".*" : "";
|
|
83
|
+
analysis.imports.push(isStatic + importText + isWildcard);
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
},
|
|
87
|
+
visitClassDeclaration: (ctx) => {
|
|
88
|
+
const classInfo = {
|
|
89
|
+
name: ctx.identifier()?.text || "Unknown",
|
|
90
|
+
type: "class",
|
|
91
|
+
modifiers: [],
|
|
92
|
+
line: getLine(ctx),
|
|
93
|
+
implements: [],
|
|
94
|
+
annotations: [],
|
|
95
|
+
methods: [],
|
|
96
|
+
fields: [],
|
|
97
|
+
innerClasses: [],
|
|
98
|
+
};
|
|
99
|
+
// Get parent modifiers
|
|
100
|
+
const parent = ctx.parent;
|
|
101
|
+
if (parent && parent.classOrInterfaceModifier) {
|
|
102
|
+
const mods = parent.classOrInterfaceModifier();
|
|
103
|
+
classInfo.modifiers = getModifiers(mods);
|
|
104
|
+
classInfo.annotations = getAnnotations(mods);
|
|
105
|
+
}
|
|
106
|
+
// Extends
|
|
107
|
+
const extendsType = ctx.typeType();
|
|
108
|
+
if (extendsType) {
|
|
109
|
+
classInfo.extends = extendsType.text;
|
|
110
|
+
}
|
|
111
|
+
// Implements
|
|
112
|
+
const typeList = ctx.typeList();
|
|
113
|
+
if (typeList && typeList.length > 0) {
|
|
114
|
+
for (const tl of typeList) {
|
|
115
|
+
const types = tl.typeType();
|
|
116
|
+
if (types) {
|
|
117
|
+
classInfo.implements.push(...types.map((t) => t.text));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Parse class body
|
|
122
|
+
const classBody = ctx.classBody();
|
|
123
|
+
if (classBody) {
|
|
124
|
+
const bodyDecls = classBody.classBodyDeclaration();
|
|
125
|
+
for (const bodyDecl of bodyDecls) {
|
|
126
|
+
const memberDecl = bodyDecl.memberDeclaration?.();
|
|
127
|
+
if (!memberDecl)
|
|
128
|
+
continue;
|
|
129
|
+
// Method
|
|
130
|
+
const methodDecl = memberDecl.methodDeclaration?.();
|
|
131
|
+
if (methodDecl) {
|
|
132
|
+
const methodInfo = {
|
|
133
|
+
name: methodDecl.identifier()?.text || "unknown",
|
|
134
|
+
returnType: methodDecl.typeTypeOrVoid()?.text || "void",
|
|
135
|
+
params: [],
|
|
136
|
+
modifiers: [],
|
|
137
|
+
line: getLine(methodDecl),
|
|
138
|
+
annotations: [],
|
|
139
|
+
throws: [],
|
|
140
|
+
complexity: 1,
|
|
141
|
+
};
|
|
142
|
+
// Get modifiers from parent
|
|
143
|
+
const modCtxs = bodyDecl.modifier?.();
|
|
144
|
+
if (modCtxs) {
|
|
145
|
+
methodInfo.modifiers = getModifiers(modCtxs);
|
|
146
|
+
methodInfo.annotations = getAnnotations(modCtxs);
|
|
147
|
+
}
|
|
148
|
+
// Parameters
|
|
149
|
+
const formalParams = methodDecl.formalParameters()?.formalParameterList?.();
|
|
150
|
+
if (formalParams) {
|
|
151
|
+
const params = formalParams.formalParameter?.();
|
|
152
|
+
if (params) {
|
|
153
|
+
for (const param of params) {
|
|
154
|
+
methodInfo.params.push({
|
|
155
|
+
name: param.variableDeclaratorId()?.text || "",
|
|
156
|
+
type: param.typeType()?.text || "",
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Throws
|
|
162
|
+
const throwsClause = methodDecl.THROWS?.();
|
|
163
|
+
if (throwsClause) {
|
|
164
|
+
const qualNames = methodDecl.qualifiedNameList()?.qualifiedName?.();
|
|
165
|
+
if (qualNames) {
|
|
166
|
+
methodInfo.throws = qualNames.map((q) => q.text);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Complexity
|
|
170
|
+
const methodBody = methodDecl.methodBody();
|
|
171
|
+
if (methodBody) {
|
|
172
|
+
methodInfo.complexity = calculateComplexity(getText(methodBody));
|
|
173
|
+
}
|
|
174
|
+
classInfo.methods.push(methodInfo);
|
|
175
|
+
}
|
|
176
|
+
// Constructor
|
|
177
|
+
const ctorDecl = memberDecl.constructorDeclaration?.();
|
|
178
|
+
if (ctorDecl) {
|
|
179
|
+
const ctorInfo = {
|
|
180
|
+
name: ctorDecl.identifier()?.text || classInfo.name,
|
|
181
|
+
returnType: classInfo.name,
|
|
182
|
+
params: [],
|
|
183
|
+
modifiers: [],
|
|
184
|
+
line: getLine(ctorDecl),
|
|
185
|
+
annotations: [],
|
|
186
|
+
throws: [],
|
|
187
|
+
complexity: 1,
|
|
188
|
+
};
|
|
189
|
+
const modCtxs = bodyDecl.modifier?.();
|
|
190
|
+
if (modCtxs) {
|
|
191
|
+
ctorInfo.modifiers = getModifiers(modCtxs);
|
|
192
|
+
ctorInfo.annotations = getAnnotations(modCtxs);
|
|
193
|
+
}
|
|
194
|
+
const formalParams = ctorDecl.formalParameters()?.formalParameterList?.();
|
|
195
|
+
if (formalParams) {
|
|
196
|
+
const params = formalParams.formalParameter?.();
|
|
197
|
+
if (params) {
|
|
198
|
+
for (const param of params) {
|
|
199
|
+
ctorInfo.params.push({
|
|
200
|
+
name: param.variableDeclaratorId()?.text || "",
|
|
201
|
+
type: param.typeType()?.text || "",
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const ctorBody = ctorDecl.block();
|
|
207
|
+
if (ctorBody) {
|
|
208
|
+
ctorInfo.complexity = calculateComplexity(getText(ctorBody));
|
|
209
|
+
}
|
|
210
|
+
classInfo.methods.push(ctorInfo);
|
|
211
|
+
}
|
|
212
|
+
// Field
|
|
213
|
+
const fieldDecl = memberDecl.fieldDeclaration?.();
|
|
214
|
+
if (fieldDecl) {
|
|
215
|
+
const varDecls = fieldDecl.variableDeclarators()?.variableDeclarator?.();
|
|
216
|
+
if (varDecls) {
|
|
217
|
+
for (const varDecl of varDecls) {
|
|
218
|
+
const fieldInfo = {
|
|
219
|
+
name: varDecl.variableDeclaratorId()?.text || "",
|
|
220
|
+
type: fieldDecl.typeType()?.text || "",
|
|
221
|
+
modifiers: [],
|
|
222
|
+
line: getLine(fieldDecl),
|
|
223
|
+
annotations: [],
|
|
224
|
+
};
|
|
225
|
+
const modCtxs = bodyDecl.modifier?.();
|
|
226
|
+
if (modCtxs) {
|
|
227
|
+
fieldInfo.modifiers = getModifiers(modCtxs);
|
|
228
|
+
fieldInfo.annotations = getAnnotations(modCtxs);
|
|
229
|
+
}
|
|
230
|
+
classInfo.fields.push(fieldInfo);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
analysis.classes.push(classInfo);
|
|
237
|
+
return null;
|
|
238
|
+
},
|
|
239
|
+
visitInterfaceDeclaration: (ctx) => {
|
|
240
|
+
const interfaceInfo = {
|
|
241
|
+
name: ctx.identifier()?.text || "Unknown",
|
|
242
|
+
type: "interface",
|
|
243
|
+
modifiers: [],
|
|
244
|
+
line: getLine(ctx),
|
|
245
|
+
implements: [],
|
|
246
|
+
annotations: [],
|
|
247
|
+
methods: [],
|
|
248
|
+
fields: [],
|
|
249
|
+
innerClasses: [],
|
|
250
|
+
};
|
|
251
|
+
// Extends (interfaces extend other interfaces)
|
|
252
|
+
const typeList = ctx.typeList();
|
|
253
|
+
if (typeList && typeList.length > 0) {
|
|
254
|
+
for (const tl of typeList) {
|
|
255
|
+
const types = tl.typeType?.();
|
|
256
|
+
if (types) {
|
|
257
|
+
interfaceInfo.implements.push(...types.map((t) => t.text));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// Parse interface body
|
|
262
|
+
const interfaceBody = ctx.interfaceBody();
|
|
263
|
+
if (interfaceBody) {
|
|
264
|
+
const bodyDecls = interfaceBody.interfaceBodyDeclaration();
|
|
265
|
+
for (const bodyDecl of bodyDecls) {
|
|
266
|
+
const memberDecl = bodyDecl.interfaceMemberDeclaration?.();
|
|
267
|
+
if (!memberDecl)
|
|
268
|
+
continue;
|
|
269
|
+
const methodDecl = memberDecl.interfaceMethodDeclaration?.();
|
|
270
|
+
if (methodDecl) {
|
|
271
|
+
const commonBody = methodDecl.interfaceCommonBodyDeclaration?.();
|
|
272
|
+
if (commonBody) {
|
|
273
|
+
const methodInfo = {
|
|
274
|
+
name: commonBody.identifier()?.text || "unknown",
|
|
275
|
+
returnType: methodDecl.interfaceMethodModifier?.()?.map((m) => m.text).join(" ") || "void",
|
|
276
|
+
params: [],
|
|
277
|
+
modifiers: [],
|
|
278
|
+
line: getLine(methodDecl),
|
|
279
|
+
annotations: [],
|
|
280
|
+
throws: [],
|
|
281
|
+
complexity: 1,
|
|
282
|
+
};
|
|
283
|
+
interfaceInfo.methods.push(methodInfo);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
analysis.classes.push(interfaceInfo);
|
|
289
|
+
return null;
|
|
290
|
+
},
|
|
291
|
+
visitEnumDeclaration: (ctx) => {
|
|
292
|
+
const enumInfo = {
|
|
293
|
+
name: ctx.identifier()?.text || "Unknown",
|
|
294
|
+
type: "enum",
|
|
295
|
+
modifiers: [],
|
|
296
|
+
line: getLine(ctx),
|
|
297
|
+
implements: [],
|
|
298
|
+
annotations: [],
|
|
299
|
+
methods: [],
|
|
300
|
+
fields: [],
|
|
301
|
+
innerClasses: [],
|
|
302
|
+
};
|
|
303
|
+
// Enum constants as fields
|
|
304
|
+
const enumConstants = ctx.enumConstants()?.enumConstant?.();
|
|
305
|
+
if (enumConstants) {
|
|
306
|
+
for (const ec of enumConstants) {
|
|
307
|
+
enumInfo.fields.push({
|
|
308
|
+
name: ec.identifier()?.text || "",
|
|
309
|
+
type: enumInfo.name,
|
|
310
|
+
modifiers: ["public", "static", "final"],
|
|
311
|
+
line: getLine(ec),
|
|
312
|
+
annotations: [],
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
analysis.classes.push(enumInfo);
|
|
317
|
+
return null;
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
visitor.visit(tree);
|
|
321
|
+
// Calculate complexity stats
|
|
322
|
+
const allMethods = analysis.classes.flatMap((c) => c.methods);
|
|
323
|
+
if (allMethods.length > 0) {
|
|
324
|
+
analysis.complexity.total = allMethods.reduce((sum, m) => sum + m.complexity, 0);
|
|
325
|
+
analysis.complexity.average = Math.round(analysis.complexity.total / allMethods.length * 10) / 10;
|
|
326
|
+
analysis.complexity.highest = allMethods.reduce((max, m) => (m.complexity > max.value ? { name: m.name, value: m.complexity } : max), { name: "", value: 0 });
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
// If parsing fails, try basic regex extraction
|
|
331
|
+
const classMatch = content.match(/(?:public\s+)?(?:abstract\s+)?(?:class|interface|enum)\s+(\w+)/);
|
|
332
|
+
if (classMatch) {
|
|
333
|
+
analysis.classes.push({
|
|
334
|
+
name: classMatch[1],
|
|
335
|
+
type: content.includes("interface ") ? "interface" : content.includes("enum ") ? "enum" : "class",
|
|
336
|
+
modifiers: [],
|
|
337
|
+
line: 1,
|
|
338
|
+
implements: [],
|
|
339
|
+
annotations: [],
|
|
340
|
+
methods: [],
|
|
341
|
+
fields: [],
|
|
342
|
+
innerClasses: [],
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return analysis;
|
|
347
|
+
}
|
|
348
|
+
// Format analysis as text
|
|
349
|
+
function formatJavaAnalysis(analysis) {
|
|
350
|
+
const lines = [];
|
|
351
|
+
lines.push(`=== ${path.basename(analysis.filepath)} ===`);
|
|
352
|
+
lines.push("");
|
|
353
|
+
if (analysis.package) {
|
|
354
|
+
lines.push(`📦 Package: ${analysis.package}`);
|
|
355
|
+
lines.push("");
|
|
356
|
+
}
|
|
357
|
+
if (analysis.imports.length > 0) {
|
|
358
|
+
lines.push("📥 Imports:");
|
|
359
|
+
// Group by prefix
|
|
360
|
+
const grouped = {};
|
|
361
|
+
for (const imp of analysis.imports) {
|
|
362
|
+
const prefix = imp.split(".").slice(0, 2).join(".");
|
|
363
|
+
if (!grouped[prefix])
|
|
364
|
+
grouped[prefix] = [];
|
|
365
|
+
grouped[prefix].push(imp);
|
|
366
|
+
}
|
|
367
|
+
for (const [prefix, imps] of Object.entries(grouped)) {
|
|
368
|
+
lines.push(` ${prefix}.* (${imps.length})`);
|
|
369
|
+
}
|
|
370
|
+
lines.push("");
|
|
371
|
+
}
|
|
372
|
+
for (const cls of analysis.classes) {
|
|
373
|
+
const icon = cls.type === "interface" ? "📋" : cls.type === "enum" ? "🔢" : "🏛️";
|
|
374
|
+
const mods = cls.modifiers.length > 0 ? cls.modifiers.join(" ") + " " : "";
|
|
375
|
+
const ext = cls.extends ? ` extends ${cls.extends}` : "";
|
|
376
|
+
const impl = cls.implements.length > 0 ? ` implements ${cls.implements.join(", ")}` : "";
|
|
377
|
+
const annots = cls.annotations.length > 0 ? cls.annotations.map((a) => `@${a}`).join(" ") + " " : "";
|
|
378
|
+
lines.push(`${icon} L${cls.line}: ${annots}${mods}${cls.type} ${cls.name}${ext}${impl}`);
|
|
379
|
+
// Fields
|
|
380
|
+
if (cls.fields.length > 0) {
|
|
381
|
+
lines.push(" 필드:");
|
|
382
|
+
for (const field of cls.fields) {
|
|
383
|
+
const fMods = field.modifiers.join(" ");
|
|
384
|
+
const fAnnots = field.annotations.length > 0 ? field.annotations.map((a) => `@${a}`).join(" ") + " " : "";
|
|
385
|
+
lines.push(` ${fAnnots}${fMods} ${field.type} ${field.name}`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Methods
|
|
389
|
+
if (cls.methods.length > 0) {
|
|
390
|
+
lines.push(" 메서드:");
|
|
391
|
+
for (const method of cls.methods) {
|
|
392
|
+
const mMods = method.modifiers.join(" ");
|
|
393
|
+
const mAnnots = method.annotations.length > 0 ? method.annotations.map((a) => `@${a}`).join(" ") + " " : "";
|
|
394
|
+
const params = method.params.map((p) => `${p.type} ${p.name}`).join(", ");
|
|
395
|
+
const throws = method.throws.length > 0 ? ` throws ${method.throws.join(", ")}` : "";
|
|
396
|
+
lines.push(` L${method.line}: ${mAnnots}${mMods} ${method.returnType} ${method.name}(${params})${throws}`);
|
|
397
|
+
lines.push(` 복잡도: ${method.complexity}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
lines.push("");
|
|
401
|
+
}
|
|
402
|
+
lines.push("📊 복잡도:");
|
|
403
|
+
lines.push(` 총합: ${analysis.complexity.total} | 평균: ${analysis.complexity.average}`);
|
|
404
|
+
if (analysis.complexity.highest.name) {
|
|
405
|
+
lines.push(` 최고: ${analysis.complexity.highest.name} (${analysis.complexity.highest.value})`);
|
|
406
|
+
}
|
|
407
|
+
return lines.join("\n");
|
|
408
|
+
}
|
|
409
|
+
// Java Analyze Tool
|
|
410
|
+
export const javaAnalyzeTool = {
|
|
411
|
+
name: "java_analyze",
|
|
412
|
+
description: "Analyze Java source file using AST parser (Java 분석). Returns classes, methods, fields, annotations, complexity. Use when user asks: 'analyze java', 'java 분석', 'Spring 분석'.",
|
|
413
|
+
parameters: {
|
|
414
|
+
type: "object",
|
|
415
|
+
required: ["filepath"],
|
|
416
|
+
properties: {
|
|
417
|
+
filepath: {
|
|
418
|
+
type: "string",
|
|
419
|
+
description: "Path to Java file (.java)",
|
|
420
|
+
},
|
|
421
|
+
format: {
|
|
422
|
+
type: "string",
|
|
423
|
+
description: "Output format: 'text' or 'json'",
|
|
424
|
+
enum: ["text", "json"],
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
handler: async (args) => {
|
|
429
|
+
try {
|
|
430
|
+
const filepath = path.resolve(args.filepath);
|
|
431
|
+
const format = args.format || "text";
|
|
432
|
+
if (!fs.existsSync(filepath)) {
|
|
433
|
+
return { success: false, content: "", error: `File not found: ${filepath}` };
|
|
434
|
+
}
|
|
435
|
+
if (!filepath.endsWith(".java")) {
|
|
436
|
+
return { success: false, content: "", error: "Not a Java file (.java required)" };
|
|
437
|
+
}
|
|
438
|
+
const content = fs.readFileSync(filepath, "utf-8");
|
|
439
|
+
const analysis = analyzeJavaFile(content, filepath);
|
|
440
|
+
if (format === "json") {
|
|
441
|
+
return { success: true, content: JSON.stringify(analysis, null, 2) };
|
|
442
|
+
}
|
|
443
|
+
return { success: true, content: formatJavaAnalysis(analysis) };
|
|
444
|
+
}
|
|
445
|
+
catch (error) {
|
|
446
|
+
return { success: false, content: "", error: String(error) };
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
// Java Complexity Report Tool
|
|
451
|
+
export const javaComplexityTool = {
|
|
452
|
+
name: "java_complexity",
|
|
453
|
+
description: "Calculate complexity for Java files (Java 복잡도 리포트). Use when user asks: 'java complexity', 'java 복잡도'.",
|
|
454
|
+
parameters: {
|
|
455
|
+
type: "object",
|
|
456
|
+
required: ["pattern"],
|
|
457
|
+
properties: {
|
|
458
|
+
pattern: {
|
|
459
|
+
type: "string",
|
|
460
|
+
description: "Glob pattern (e.g., src/**/*.java)",
|
|
461
|
+
},
|
|
462
|
+
threshold: {
|
|
463
|
+
type: "number",
|
|
464
|
+
description: "Only show methods with complexity >= threshold (default: 5)",
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
handler: async (args) => {
|
|
469
|
+
try {
|
|
470
|
+
const { glob } = await import("glob");
|
|
471
|
+
const pattern = args.pattern;
|
|
472
|
+
const threshold = args.threshold || 5;
|
|
473
|
+
const files = await glob(pattern, {
|
|
474
|
+
ignore: ["**/node_modules/**", "**/target/**", "**/build/**"],
|
|
475
|
+
});
|
|
476
|
+
const allMethods = [];
|
|
477
|
+
for (const file of files) {
|
|
478
|
+
if (!file.endsWith(".java"))
|
|
479
|
+
continue;
|
|
480
|
+
try {
|
|
481
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
482
|
+
const analysis = analyzeJavaFile(content, file);
|
|
483
|
+
for (const cls of analysis.classes) {
|
|
484
|
+
for (const method of cls.methods) {
|
|
485
|
+
allMethods.push({
|
|
486
|
+
file: path.relative(process.cwd(), file),
|
|
487
|
+
class: cls.name,
|
|
488
|
+
method: method.name,
|
|
489
|
+
complexity: method.complexity,
|
|
490
|
+
line: method.line,
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
catch {
|
|
496
|
+
// Skip unparseable files
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
// Sort by complexity
|
|
500
|
+
allMethods.sort((a, b) => b.complexity - a.complexity);
|
|
501
|
+
const high = allMethods.filter((m) => m.complexity >= threshold);
|
|
502
|
+
const total = allMethods.reduce((sum, m) => sum + m.complexity, 0);
|
|
503
|
+
const avg = allMethods.length > 0 ? Math.round(total / allMethods.length * 10) / 10 : 0;
|
|
504
|
+
const lines = [];
|
|
505
|
+
lines.push("=== Java 복잡도 리포트 ===");
|
|
506
|
+
lines.push("");
|
|
507
|
+
lines.push("📊 통계:");
|
|
508
|
+
lines.push(` 총 메서드: ${allMethods.length}개`);
|
|
509
|
+
lines.push(` 평균 복잡도: ${avg}`);
|
|
510
|
+
lines.push(` 높은 복잡도 (>= ${threshold}): ${high.length}개`);
|
|
511
|
+
lines.push("");
|
|
512
|
+
if (high.length > 0) {
|
|
513
|
+
lines.push(`⚠️ 복잡도 높은 메서드 (>= ${threshold}):`);
|
|
514
|
+
for (const m of high.slice(0, 20)) {
|
|
515
|
+
const bar = "█".repeat(Math.min(m.complexity, 20));
|
|
516
|
+
lines.push(` ${m.complexity.toString().padStart(2)} ${bar} ${m.class}.${m.method}()`);
|
|
517
|
+
lines.push(` ${m.file}:${m.line}`);
|
|
518
|
+
}
|
|
519
|
+
if (high.length > 20) {
|
|
520
|
+
lines.push(` ... 외 ${high.length - 20}개`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
lines.push(`✅ 복잡도 ${threshold} 이상인 메서드가 없습니다.`);
|
|
525
|
+
}
|
|
526
|
+
return { success: true, content: lines.join("\n") };
|
|
527
|
+
}
|
|
528
|
+
catch (error) {
|
|
529
|
+
return { success: false, content: "", error: String(error) };
|
|
530
|
+
}
|
|
531
|
+
},
|
|
532
|
+
};
|
|
533
|
+
// Spring Pattern Check Tool
|
|
534
|
+
export const springCheckTool = {
|
|
535
|
+
name: "spring_check",
|
|
536
|
+
description: "Check Spring framework patterns (Spring 패턴 검사). Finds controllers, services, repositories, configuration. Use when user asks: 'spring check', 'spring 분석', 'Spring 패턴'.",
|
|
537
|
+
parameters: {
|
|
538
|
+
type: "object",
|
|
539
|
+
required: ["pattern"],
|
|
540
|
+
properties: {
|
|
541
|
+
pattern: {
|
|
542
|
+
type: "string",
|
|
543
|
+
description: "Glob pattern (e.g., src/**/*.java)",
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
handler: async (args) => {
|
|
548
|
+
try {
|
|
549
|
+
const { glob } = await import("glob");
|
|
550
|
+
const pattern = args.pattern;
|
|
551
|
+
const files = await glob(pattern, {
|
|
552
|
+
ignore: ["**/node_modules/**", "**/target/**", "**/build/**"],
|
|
553
|
+
});
|
|
554
|
+
const springComponents = {
|
|
555
|
+
controllers: [],
|
|
556
|
+
services: [],
|
|
557
|
+
repositories: [],
|
|
558
|
+
components: [],
|
|
559
|
+
configurations: [],
|
|
560
|
+
entities: [],
|
|
561
|
+
};
|
|
562
|
+
for (const file of files) {
|
|
563
|
+
if (!file.endsWith(".java"))
|
|
564
|
+
continue;
|
|
565
|
+
try {
|
|
566
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
567
|
+
const analysis = analyzeJavaFile(content, file);
|
|
568
|
+
const relativePath = path.relative(process.cwd(), file);
|
|
569
|
+
for (const cls of analysis.classes) {
|
|
570
|
+
const annotations = cls.annotations.map((a) => a.toLowerCase());
|
|
571
|
+
// Controller
|
|
572
|
+
if (annotations.some((a) => a.includes("controller") || a.includes("restcontroller"))) {
|
|
573
|
+
const mappings = [];
|
|
574
|
+
// Find request mappings in methods
|
|
575
|
+
for (const method of cls.methods) {
|
|
576
|
+
const methodAnnots = method.annotations.join(" ").toLowerCase();
|
|
577
|
+
if (methodAnnots.includes("mapping")) {
|
|
578
|
+
mappings.push(`${method.name}()`);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
springComponents.controllers.push({ file: relativePath, class: cls.name, mappings });
|
|
582
|
+
}
|
|
583
|
+
// Service
|
|
584
|
+
if (annotations.some((a) => a.includes("service"))) {
|
|
585
|
+
springComponents.services.push({ file: relativePath, class: cls.name });
|
|
586
|
+
}
|
|
587
|
+
// Repository
|
|
588
|
+
if (annotations.some((a) => a.includes("repository"))) {
|
|
589
|
+
springComponents.repositories.push({ file: relativePath, class: cls.name });
|
|
590
|
+
}
|
|
591
|
+
// Component
|
|
592
|
+
if (annotations.some((a) => a === "component")) {
|
|
593
|
+
springComponents.components.push({ file: relativePath, class: cls.name });
|
|
594
|
+
}
|
|
595
|
+
// Configuration
|
|
596
|
+
if (annotations.some((a) => a.includes("configuration"))) {
|
|
597
|
+
springComponents.configurations.push({ file: relativePath, class: cls.name });
|
|
598
|
+
}
|
|
599
|
+
// Entity
|
|
600
|
+
if (annotations.some((a) => a.includes("entity") || a.includes("table"))) {
|
|
601
|
+
springComponents.entities.push({ file: relativePath, class: cls.name });
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
catch {
|
|
606
|
+
// Skip unparseable files
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
const lines = [];
|
|
610
|
+
lines.push("=== Spring 패턴 분석 ===");
|
|
611
|
+
lines.push("");
|
|
612
|
+
if (springComponents.controllers.length > 0) {
|
|
613
|
+
lines.push(`🎮 Controllers (${springComponents.controllers.length}):`);
|
|
614
|
+
for (const c of springComponents.controllers) {
|
|
615
|
+
lines.push(` ${c.class}`);
|
|
616
|
+
lines.push(` ${c.file}`);
|
|
617
|
+
if (c.mappings.length > 0) {
|
|
618
|
+
lines.push(` 엔드포인트: ${c.mappings.join(", ")}`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
lines.push("");
|
|
622
|
+
}
|
|
623
|
+
if (springComponents.services.length > 0) {
|
|
624
|
+
lines.push(`⚙️ Services (${springComponents.services.length}):`);
|
|
625
|
+
for (const s of springComponents.services) {
|
|
626
|
+
lines.push(` ${s.class} - ${s.file}`);
|
|
627
|
+
}
|
|
628
|
+
lines.push("");
|
|
629
|
+
}
|
|
630
|
+
if (springComponents.repositories.length > 0) {
|
|
631
|
+
lines.push(`🗄️ Repositories (${springComponents.repositories.length}):`);
|
|
632
|
+
for (const r of springComponents.repositories) {
|
|
633
|
+
lines.push(` ${r.class} - ${r.file}`);
|
|
634
|
+
}
|
|
635
|
+
lines.push("");
|
|
636
|
+
}
|
|
637
|
+
if (springComponents.entities.length > 0) {
|
|
638
|
+
lines.push(`📋 Entities (${springComponents.entities.length}):`);
|
|
639
|
+
for (const e of springComponents.entities) {
|
|
640
|
+
lines.push(` ${e.class} - ${e.file}`);
|
|
641
|
+
}
|
|
642
|
+
lines.push("");
|
|
643
|
+
}
|
|
644
|
+
if (springComponents.configurations.length > 0) {
|
|
645
|
+
lines.push(`🔧 Configurations (${springComponents.configurations.length}):`);
|
|
646
|
+
for (const c of springComponents.configurations) {
|
|
647
|
+
lines.push(` ${c.class} - ${c.file}`);
|
|
648
|
+
}
|
|
649
|
+
lines.push("");
|
|
650
|
+
}
|
|
651
|
+
if (springComponents.components.length > 0) {
|
|
652
|
+
lines.push(`📦 Components (${springComponents.components.length}):`);
|
|
653
|
+
for (const c of springComponents.components) {
|
|
654
|
+
lines.push(` ${c.class} - ${c.file}`);
|
|
655
|
+
}
|
|
656
|
+
lines.push("");
|
|
657
|
+
}
|
|
658
|
+
const total = Object.values(springComponents).reduce((sum, arr) => sum + arr.length, 0);
|
|
659
|
+
if (total === 0) {
|
|
660
|
+
lines.push("Spring 컴포넌트를 찾지 못했습니다.");
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
lines.push(`📊 총 ${total}개 Spring 컴포넌트 발견`);
|
|
664
|
+
}
|
|
665
|
+
return { success: true, content: lines.join("\n") };
|
|
666
|
+
}
|
|
667
|
+
catch (error) {
|
|
668
|
+
return { success: false, content: "", error: String(error) };
|
|
669
|
+
}
|
|
670
|
+
},
|
|
671
|
+
};
|
|
672
|
+
// Export all Java tools
|
|
673
|
+
export const javaTools = [
|
|
674
|
+
javaAnalyzeTool,
|
|
675
|
+
javaComplexityTool,
|
|
676
|
+
springCheckTool,
|
|
677
|
+
];
|
|
678
|
+
//# sourceMappingURL=javaAst.js.map
|