ucn 3.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.
Potentially problematic release.
This version of ucn might be problematic. Click here for more details.
- package/.claude/skills/ucn/SKILL.md +77 -0
- package/LICENSE +21 -0
- package/README.md +135 -0
- package/cli/index.js +2437 -0
- package/core/discovery.js +513 -0
- package/core/imports.js +558 -0
- package/core/output.js +1274 -0
- package/core/parser.js +279 -0
- package/core/project.js +3261 -0
- package/index.js +52 -0
- package/languages/go.js +653 -0
- package/languages/index.js +267 -0
- package/languages/java.js +826 -0
- package/languages/javascript.js +1346 -0
- package/languages/python.js +667 -0
- package/languages/rust.js +950 -0
- package/languages/utils.js +457 -0
- package/package.json +42 -0
- package/test/fixtures/go/go.mod +3 -0
- package/test/fixtures/go/main.go +257 -0
- package/test/fixtures/go/service.go +187 -0
- package/test/fixtures/java/DataService.java +279 -0
- package/test/fixtures/java/Main.java +287 -0
- package/test/fixtures/java/Utils.java +199 -0
- package/test/fixtures/java/pom.xml +6 -0
- package/test/fixtures/javascript/main.js +109 -0
- package/test/fixtures/javascript/package.json +1 -0
- package/test/fixtures/javascript/service.js +88 -0
- package/test/fixtures/javascript/utils.js +67 -0
- package/test/fixtures/python/main.py +198 -0
- package/test/fixtures/python/pyproject.toml +3 -0
- package/test/fixtures/python/service.py +166 -0
- package/test/fixtures/python/utils.py +118 -0
- package/test/fixtures/rust/Cargo.toml +3 -0
- package/test/fixtures/rust/main.rs +253 -0
- package/test/fixtures/rust/service.rs +210 -0
- package/test/fixtures/rust/utils.rs +154 -0
- package/test/fixtures/typescript/main.ts +154 -0
- package/test/fixtures/typescript/package.json +1 -0
- package/test/fixtures/typescript/repository.ts +149 -0
- package/test/fixtures/typescript/types.ts +114 -0
- package/test/parser.test.js +3661 -0
- package/test/public-repos-test.js +477 -0
- package/test/systematic-test.js +619 -0
- package/ucn.js +8 -0
|
@@ -0,0 +1,826 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* languages/java.js - Tree-sitter based Java parsing
|
|
3
|
+
*
|
|
4
|
+
* Handles: method declarations, constructors, class/interface/enum/record
|
|
5
|
+
* declarations, and static final constants.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
traverseTree,
|
|
10
|
+
nodeToLocation,
|
|
11
|
+
parseStructuredParams,
|
|
12
|
+
extractJavaDocstring
|
|
13
|
+
} = require('./utils');
|
|
14
|
+
const { PARSE_OPTIONS } = require('./index');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Extract Java parameters
|
|
18
|
+
*/
|
|
19
|
+
function extractJavaParams(paramsNode) {
|
|
20
|
+
if (!paramsNode) return '...';
|
|
21
|
+
const text = paramsNode.text;
|
|
22
|
+
let params = text.replace(/^\(|\)$/g, '').trim();
|
|
23
|
+
if (!params) return '...';
|
|
24
|
+
return params;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Extract modifiers from a node
|
|
29
|
+
*/
|
|
30
|
+
function extractModifiers(node) {
|
|
31
|
+
const modifiers = [];
|
|
32
|
+
const modifiersNode = node.childForFieldName('modifiers');
|
|
33
|
+
|
|
34
|
+
if (modifiersNode) {
|
|
35
|
+
for (let i = 0; i < modifiersNode.namedChildCount; i++) {
|
|
36
|
+
const mod = modifiersNode.namedChild(i);
|
|
37
|
+
if (mod.type === 'marker_annotation' || mod.type === 'annotation') {
|
|
38
|
+
continue; // Skip annotations for modifiers
|
|
39
|
+
}
|
|
40
|
+
modifiers.push(mod.text);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Also check first line for modifiers
|
|
45
|
+
const text = node.text;
|
|
46
|
+
const firstLine = text.split('\n')[0];
|
|
47
|
+
const keywords = ['public', 'private', 'protected', 'static', 'final', 'abstract', 'synchronized', 'native', 'default'];
|
|
48
|
+
for (const kw of keywords) {
|
|
49
|
+
if (firstLine.includes(kw + ' ') && !modifiers.includes(kw)) {
|
|
50
|
+
modifiers.push(kw);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return [...new Set(modifiers)];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Extract annotations from a node
|
|
59
|
+
*/
|
|
60
|
+
function extractAnnotations(node) {
|
|
61
|
+
const annotations = [];
|
|
62
|
+
const modifiersNode = node.childForFieldName('modifiers');
|
|
63
|
+
|
|
64
|
+
if (modifiersNode) {
|
|
65
|
+
for (let i = 0; i < modifiersNode.namedChildCount; i++) {
|
|
66
|
+
const mod = modifiersNode.namedChild(i);
|
|
67
|
+
if (mod.type === 'marker_annotation' || mod.type === 'annotation') {
|
|
68
|
+
annotations.push(mod.text);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return annotations;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Extract return type from method
|
|
78
|
+
*/
|
|
79
|
+
function extractReturnType(node) {
|
|
80
|
+
const typeNode = node.childForFieldName('type');
|
|
81
|
+
if (typeNode) {
|
|
82
|
+
return typeNode.text;
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Extract generics/type parameters
|
|
89
|
+
*/
|
|
90
|
+
function extractGenerics(node) {
|
|
91
|
+
const typeParamsNode = node.childForFieldName('type_parameters');
|
|
92
|
+
if (typeParamsNode) {
|
|
93
|
+
return typeParamsNode.text;
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Find all methods/constructors in Java code using tree-sitter
|
|
100
|
+
*/
|
|
101
|
+
function findFunctions(code, parser) {
|
|
102
|
+
const tree = parser.parse(code, undefined, PARSE_OPTIONS);
|
|
103
|
+
const functions = [];
|
|
104
|
+
const processedRanges = new Set();
|
|
105
|
+
|
|
106
|
+
traverseTree(tree.rootNode, (node) => {
|
|
107
|
+
const rangeKey = `${node.startIndex}-${node.endIndex}`;
|
|
108
|
+
|
|
109
|
+
// Method declarations
|
|
110
|
+
if (node.type === 'method_declaration') {
|
|
111
|
+
if (processedRanges.has(rangeKey)) return true;
|
|
112
|
+
processedRanges.add(rangeKey);
|
|
113
|
+
|
|
114
|
+
const nameNode = node.childForFieldName('name');
|
|
115
|
+
const paramsNode = node.childForFieldName('parameters');
|
|
116
|
+
|
|
117
|
+
if (nameNode) {
|
|
118
|
+
const { startLine, endLine, indent } = nodeToLocation(node, code);
|
|
119
|
+
const modifiers = extractModifiers(node);
|
|
120
|
+
const annotations = extractAnnotations(node);
|
|
121
|
+
const returnType = extractReturnType(node);
|
|
122
|
+
const generics = extractGenerics(node);
|
|
123
|
+
const docstring = extractJavaDocstring(code, startLine);
|
|
124
|
+
|
|
125
|
+
functions.push({
|
|
126
|
+
name: nameNode.text,
|
|
127
|
+
params: extractJavaParams(paramsNode),
|
|
128
|
+
paramsStructured: parseStructuredParams(paramsNode, 'java'),
|
|
129
|
+
startLine,
|
|
130
|
+
endLine,
|
|
131
|
+
indent,
|
|
132
|
+
modifiers,
|
|
133
|
+
...(returnType && { returnType }),
|
|
134
|
+
...(generics && { generics }),
|
|
135
|
+
...(docstring && { docstring }),
|
|
136
|
+
...(annotations.length > 0 && { annotations })
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Constructor declarations
|
|
143
|
+
if (node.type === 'constructor_declaration') {
|
|
144
|
+
if (processedRanges.has(rangeKey)) return true;
|
|
145
|
+
processedRanges.add(rangeKey);
|
|
146
|
+
|
|
147
|
+
const nameNode = node.childForFieldName('name');
|
|
148
|
+
const paramsNode = node.childForFieldName('parameters');
|
|
149
|
+
|
|
150
|
+
if (nameNode) {
|
|
151
|
+
const { startLine, endLine, indent } = nodeToLocation(node, code);
|
|
152
|
+
const modifiers = extractModifiers(node);
|
|
153
|
+
const annotations = extractAnnotations(node);
|
|
154
|
+
const docstring = extractJavaDocstring(code, startLine);
|
|
155
|
+
|
|
156
|
+
functions.push({
|
|
157
|
+
name: nameNode.text,
|
|
158
|
+
params: extractJavaParams(paramsNode),
|
|
159
|
+
paramsStructured: parseStructuredParams(paramsNode, 'java'),
|
|
160
|
+
startLine,
|
|
161
|
+
endLine,
|
|
162
|
+
indent,
|
|
163
|
+
modifiers,
|
|
164
|
+
isConstructor: true,
|
|
165
|
+
...(docstring && { docstring }),
|
|
166
|
+
...(annotations.length > 0 && { annotations })
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return true;
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
functions.sort((a, b) => a.startLine - b.startLine);
|
|
176
|
+
return functions;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Find all classes, interfaces, enums, records in Java code
|
|
181
|
+
*/
|
|
182
|
+
function findClasses(code, parser) {
|
|
183
|
+
const tree = parser.parse(code, undefined, PARSE_OPTIONS);
|
|
184
|
+
const classes = [];
|
|
185
|
+
const processedRanges = new Set();
|
|
186
|
+
|
|
187
|
+
traverseTree(tree.rootNode, (node) => {
|
|
188
|
+
const rangeKey = `${node.startIndex}-${node.endIndex}`;
|
|
189
|
+
|
|
190
|
+
// Class declarations
|
|
191
|
+
if (node.type === 'class_declaration') {
|
|
192
|
+
if (processedRanges.has(rangeKey)) return true;
|
|
193
|
+
processedRanges.add(rangeKey);
|
|
194
|
+
|
|
195
|
+
const nameNode = node.childForFieldName('name');
|
|
196
|
+
if (nameNode) {
|
|
197
|
+
const { startLine, endLine } = nodeToLocation(node, code);
|
|
198
|
+
const members = extractClassMembers(node, code);
|
|
199
|
+
const modifiers = extractModifiers(node);
|
|
200
|
+
const annotations = extractAnnotations(node);
|
|
201
|
+
const docstring = extractJavaDocstring(code, startLine);
|
|
202
|
+
const generics = extractGenerics(node);
|
|
203
|
+
const extendsInfo = extractExtends(node);
|
|
204
|
+
const implementsInfo = extractImplements(node);
|
|
205
|
+
|
|
206
|
+
classes.push({
|
|
207
|
+
name: nameNode.text,
|
|
208
|
+
startLine,
|
|
209
|
+
endLine,
|
|
210
|
+
type: 'class',
|
|
211
|
+
members,
|
|
212
|
+
modifiers,
|
|
213
|
+
...(docstring && { docstring }),
|
|
214
|
+
...(generics && { generics }),
|
|
215
|
+
...(annotations.length > 0 && { annotations }),
|
|
216
|
+
...(extendsInfo && { extends: extendsInfo }),
|
|
217
|
+
...(implementsInfo.length > 0 && { implements: implementsInfo })
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
return false; // Don't traverse into class body
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Interface declarations
|
|
224
|
+
if (node.type === 'interface_declaration') {
|
|
225
|
+
if (processedRanges.has(rangeKey)) return true;
|
|
226
|
+
processedRanges.add(rangeKey);
|
|
227
|
+
|
|
228
|
+
const nameNode = node.childForFieldName('name');
|
|
229
|
+
if (nameNode) {
|
|
230
|
+
const { startLine, endLine } = nodeToLocation(node, code);
|
|
231
|
+
const modifiers = extractModifiers(node);
|
|
232
|
+
const annotations = extractAnnotations(node);
|
|
233
|
+
const docstring = extractJavaDocstring(code, startLine);
|
|
234
|
+
const generics = extractGenerics(node);
|
|
235
|
+
const extendsInfo = extractInterfaceExtends(node);
|
|
236
|
+
|
|
237
|
+
classes.push({
|
|
238
|
+
name: nameNode.text,
|
|
239
|
+
startLine,
|
|
240
|
+
endLine,
|
|
241
|
+
type: 'interface',
|
|
242
|
+
members: [],
|
|
243
|
+
modifiers,
|
|
244
|
+
...(docstring && { docstring }),
|
|
245
|
+
...(generics && { generics }),
|
|
246
|
+
...(annotations.length > 0 && { annotations }),
|
|
247
|
+
...(extendsInfo.length > 0 && { extends: extendsInfo.join(', ') })
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Enum declarations
|
|
254
|
+
if (node.type === 'enum_declaration') {
|
|
255
|
+
if (processedRanges.has(rangeKey)) return true;
|
|
256
|
+
processedRanges.add(rangeKey);
|
|
257
|
+
|
|
258
|
+
const nameNode = node.childForFieldName('name');
|
|
259
|
+
if (nameNode) {
|
|
260
|
+
const { startLine, endLine } = nodeToLocation(node, code);
|
|
261
|
+
const modifiers = extractModifiers(node);
|
|
262
|
+
const annotations = extractAnnotations(node);
|
|
263
|
+
const docstring = extractJavaDocstring(code, startLine);
|
|
264
|
+
|
|
265
|
+
classes.push({
|
|
266
|
+
name: nameNode.text,
|
|
267
|
+
startLine,
|
|
268
|
+
endLine,
|
|
269
|
+
type: 'enum',
|
|
270
|
+
members: [],
|
|
271
|
+
modifiers,
|
|
272
|
+
...(docstring && { docstring }),
|
|
273
|
+
...(annotations.length > 0 && { annotations })
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Record declarations (Java 14+)
|
|
280
|
+
if (node.type === 'record_declaration') {
|
|
281
|
+
if (processedRanges.has(rangeKey)) return true;
|
|
282
|
+
processedRanges.add(rangeKey);
|
|
283
|
+
|
|
284
|
+
const nameNode = node.childForFieldName('name');
|
|
285
|
+
if (nameNode) {
|
|
286
|
+
const { startLine, endLine } = nodeToLocation(node, code);
|
|
287
|
+
const modifiers = extractModifiers(node);
|
|
288
|
+
const annotations = extractAnnotations(node);
|
|
289
|
+
const docstring = extractJavaDocstring(code, startLine);
|
|
290
|
+
const generics = extractGenerics(node);
|
|
291
|
+
|
|
292
|
+
classes.push({
|
|
293
|
+
name: nameNode.text,
|
|
294
|
+
startLine,
|
|
295
|
+
endLine,
|
|
296
|
+
type: 'record',
|
|
297
|
+
members: [],
|
|
298
|
+
modifiers,
|
|
299
|
+
...(docstring && { docstring }),
|
|
300
|
+
...(generics && { generics }),
|
|
301
|
+
...(annotations.length > 0 && { annotations })
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return true;
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
classes.sort((a, b) => a.startLine - b.startLine);
|
|
311
|
+
return classes;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Extract extends clause from class
|
|
316
|
+
*/
|
|
317
|
+
function extractExtends(classNode) {
|
|
318
|
+
const superclassNode = classNode.childForFieldName('superclass');
|
|
319
|
+
if (superclassNode) {
|
|
320
|
+
return superclassNode.text;
|
|
321
|
+
}
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Extract implements clause from class
|
|
327
|
+
*/
|
|
328
|
+
function extractImplements(classNode) {
|
|
329
|
+
const interfacesNode = classNode.childForFieldName('interfaces');
|
|
330
|
+
if (interfacesNode) {
|
|
331
|
+
const interfaces = [];
|
|
332
|
+
for (let i = 0; i < interfacesNode.namedChildCount; i++) {
|
|
333
|
+
const iface = interfacesNode.namedChild(i);
|
|
334
|
+
if (iface.type === 'type_identifier' || iface.type === 'generic_type') {
|
|
335
|
+
interfaces.push(iface.text);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return interfaces;
|
|
339
|
+
}
|
|
340
|
+
return [];
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Extract extends from interface
|
|
345
|
+
*/
|
|
346
|
+
function extractInterfaceExtends(interfaceNode) {
|
|
347
|
+
const extendsNode = interfaceNode.childForFieldName('extends');
|
|
348
|
+
if (extendsNode) {
|
|
349
|
+
const interfaces = [];
|
|
350
|
+
for (let i = 0; i < extendsNode.namedChildCount; i++) {
|
|
351
|
+
const iface = extendsNode.namedChild(i);
|
|
352
|
+
interfaces.push(iface.text);
|
|
353
|
+
}
|
|
354
|
+
return interfaces;
|
|
355
|
+
}
|
|
356
|
+
return [];
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Extract class members (methods, constructors)
|
|
361
|
+
*/
|
|
362
|
+
function extractClassMembers(classNode, code) {
|
|
363
|
+
const members = [];
|
|
364
|
+
const bodyNode = classNode.childForFieldName('body');
|
|
365
|
+
if (!bodyNode) return members;
|
|
366
|
+
|
|
367
|
+
for (let i = 0; i < bodyNode.namedChildCount; i++) {
|
|
368
|
+
const child = bodyNode.namedChild(i);
|
|
369
|
+
|
|
370
|
+
// Method declarations
|
|
371
|
+
if (child.type === 'method_declaration') {
|
|
372
|
+
const nameNode = child.childForFieldName('name');
|
|
373
|
+
const paramsNode = child.childForFieldName('parameters');
|
|
374
|
+
|
|
375
|
+
if (nameNode) {
|
|
376
|
+
const { startLine, endLine } = nodeToLocation(child, code);
|
|
377
|
+
const modifiers = extractModifiers(child);
|
|
378
|
+
const returnType = extractReturnType(child);
|
|
379
|
+
const docstring = extractJavaDocstring(code, startLine);
|
|
380
|
+
|
|
381
|
+
let memberType = 'method';
|
|
382
|
+
if (modifiers.includes('static')) {
|
|
383
|
+
memberType = 'static';
|
|
384
|
+
} else if (modifiers.includes('abstract')) {
|
|
385
|
+
memberType = 'abstract';
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
members.push({
|
|
389
|
+
name: nameNode.text,
|
|
390
|
+
params: extractJavaParams(paramsNode),
|
|
391
|
+
paramsStructured: parseStructuredParams(paramsNode, 'java'),
|
|
392
|
+
startLine,
|
|
393
|
+
endLine,
|
|
394
|
+
memberType,
|
|
395
|
+
modifiers,
|
|
396
|
+
...(returnType && { returnType }),
|
|
397
|
+
...(docstring && { docstring })
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Constructor declarations
|
|
403
|
+
if (child.type === 'constructor_declaration') {
|
|
404
|
+
const nameNode = child.childForFieldName('name');
|
|
405
|
+
const paramsNode = child.childForFieldName('parameters');
|
|
406
|
+
|
|
407
|
+
if (nameNode) {
|
|
408
|
+
const { startLine, endLine } = nodeToLocation(child, code);
|
|
409
|
+
const modifiers = extractModifiers(child);
|
|
410
|
+
const docstring = extractJavaDocstring(code, startLine);
|
|
411
|
+
|
|
412
|
+
members.push({
|
|
413
|
+
name: nameNode.text,
|
|
414
|
+
params: extractJavaParams(paramsNode),
|
|
415
|
+
paramsStructured: parseStructuredParams(paramsNode, 'java'),
|
|
416
|
+
startLine,
|
|
417
|
+
endLine,
|
|
418
|
+
memberType: 'constructor',
|
|
419
|
+
modifiers,
|
|
420
|
+
...(docstring && { docstring })
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return members;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Find state objects (static final constants) in Java code
|
|
431
|
+
*/
|
|
432
|
+
function findStateObjects(code, parser) {
|
|
433
|
+
const tree = parser.parse(code, undefined, PARSE_OPTIONS);
|
|
434
|
+
const objects = [];
|
|
435
|
+
|
|
436
|
+
const statePattern = /^([A-Z][A-Z0-9_]+|[A-Z][a-zA-Z]*(?:CONFIG|SETTINGS|OPTIONS))$/;
|
|
437
|
+
|
|
438
|
+
traverseTree(tree.rootNode, (node) => {
|
|
439
|
+
if (node.type === 'field_declaration') {
|
|
440
|
+
const modifiers = extractModifiers(node);
|
|
441
|
+
if (modifiers.includes('static') && modifiers.includes('final')) {
|
|
442
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
443
|
+
const child = node.namedChild(i);
|
|
444
|
+
if (child.type === 'variable_declarator') {
|
|
445
|
+
const nameNode = child.childForFieldName('name');
|
|
446
|
+
const valueNode = child.childForFieldName('value');
|
|
447
|
+
|
|
448
|
+
if (nameNode && valueNode) {
|
|
449
|
+
const name = nameNode.text;
|
|
450
|
+
if (statePattern.test(name)) {
|
|
451
|
+
const { startLine, endLine } = nodeToLocation(node, code);
|
|
452
|
+
objects.push({ name, startLine, endLine });
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return true;
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
objects.sort((a, b) => a.startLine - b.startLine);
|
|
465
|
+
return objects;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Parse a Java file completely
|
|
470
|
+
*/
|
|
471
|
+
function parse(code, parser) {
|
|
472
|
+
return {
|
|
473
|
+
language: 'java',
|
|
474
|
+
totalLines: code.split('\n').length,
|
|
475
|
+
functions: findFunctions(code, parser),
|
|
476
|
+
classes: findClasses(code, parser),
|
|
477
|
+
stateObjects: findStateObjects(code, parser),
|
|
478
|
+
imports: [],
|
|
479
|
+
exports: []
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Find all function/method calls in Java code using tree-sitter AST
|
|
485
|
+
* @param {string} code - Source code to analyze
|
|
486
|
+
* @param {object} parser - Tree-sitter parser instance
|
|
487
|
+
* @returns {Array<{name: string, line: number, isMethod: boolean, receiver?: string, isConstructor?: boolean}>}
|
|
488
|
+
*/
|
|
489
|
+
function findCallsInCode(code, parser) {
|
|
490
|
+
const tree = parser.parse(code, undefined, PARSE_OPTIONS);
|
|
491
|
+
const calls = [];
|
|
492
|
+
const functionStack = []; // Stack of { name, startLine, endLine }
|
|
493
|
+
|
|
494
|
+
// Helper to check if a node creates a function scope
|
|
495
|
+
const isFunctionNode = (node) => {
|
|
496
|
+
return ['method_declaration', 'constructor_declaration', 'lambda_expression'].includes(node.type);
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
// Helper to extract function name from a function node
|
|
500
|
+
const extractFunctionName = (node) => {
|
|
501
|
+
if (node.type === 'method_declaration') {
|
|
502
|
+
const nameNode = node.childForFieldName('name');
|
|
503
|
+
return nameNode?.text || '<anonymous>';
|
|
504
|
+
}
|
|
505
|
+
if (node.type === 'constructor_declaration') {
|
|
506
|
+
const nameNode = node.childForFieldName('name');
|
|
507
|
+
return nameNode?.text || '<constructor>';
|
|
508
|
+
}
|
|
509
|
+
if (node.type === 'lambda_expression') {
|
|
510
|
+
return '<lambda>';
|
|
511
|
+
}
|
|
512
|
+
return '<anonymous>';
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
// Helper to get current enclosing function
|
|
516
|
+
const getCurrentEnclosingFunction = () => {
|
|
517
|
+
return functionStack.length > 0
|
|
518
|
+
? { ...functionStack[functionStack.length - 1] }
|
|
519
|
+
: null;
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
traverseTree(tree.rootNode, (node) => {
|
|
523
|
+
// Track function entry
|
|
524
|
+
if (isFunctionNode(node)) {
|
|
525
|
+
functionStack.push({
|
|
526
|
+
name: extractFunctionName(node),
|
|
527
|
+
startLine: node.startPosition.row + 1,
|
|
528
|
+
endLine: node.endPosition.row + 1
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Handle method invocations: foo(), obj.foo(), this.foo()
|
|
533
|
+
if (node.type === 'method_invocation') {
|
|
534
|
+
const nameNode = node.childForFieldName('name');
|
|
535
|
+
const objNode = node.childForFieldName('object');
|
|
536
|
+
|
|
537
|
+
if (nameNode) {
|
|
538
|
+
const enclosingFunction = getCurrentEnclosingFunction();
|
|
539
|
+
calls.push({
|
|
540
|
+
name: nameNode.text,
|
|
541
|
+
line: node.startPosition.row + 1,
|
|
542
|
+
isMethod: !!objNode,
|
|
543
|
+
receiver: objNode?.type === 'identifier' ? objNode.text : undefined,
|
|
544
|
+
enclosingFunction
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
return true;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Handle constructor calls: new Foo(), new pkg.Bar()
|
|
551
|
+
if (node.type === 'object_creation_expression') {
|
|
552
|
+
const typeNode = node.childForFieldName('type');
|
|
553
|
+
if (typeNode) {
|
|
554
|
+
let typeName = typeNode.text;
|
|
555
|
+
// Handle generic types like List<String>
|
|
556
|
+
const genericIdx = typeName.indexOf('<');
|
|
557
|
+
if (genericIdx > 0) {
|
|
558
|
+
typeName = typeName.substring(0, genericIdx);
|
|
559
|
+
}
|
|
560
|
+
// Handle qualified names like pkg.Class
|
|
561
|
+
const dotIdx = typeName.lastIndexOf('.');
|
|
562
|
+
if (dotIdx > 0) {
|
|
563
|
+
typeName = typeName.substring(dotIdx + 1);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const enclosingFunction = getCurrentEnclosingFunction();
|
|
567
|
+
calls.push({
|
|
568
|
+
name: typeName,
|
|
569
|
+
line: node.startPosition.row + 1,
|
|
570
|
+
isMethod: false,
|
|
571
|
+
isConstructor: true,
|
|
572
|
+
enclosingFunction
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
return true;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return true;
|
|
579
|
+
}, {
|
|
580
|
+
onLeave: (node) => {
|
|
581
|
+
if (isFunctionNode(node)) {
|
|
582
|
+
functionStack.pop();
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
return calls;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Find all imports in Java code using tree-sitter AST
|
|
592
|
+
* @param {string} code - Source code to analyze
|
|
593
|
+
* @param {object} parser - Tree-sitter parser instance
|
|
594
|
+
* @returns {Array<{module: string, names: string[], type: string, line: number}>}
|
|
595
|
+
*/
|
|
596
|
+
function findImportsInCode(code, parser) {
|
|
597
|
+
const tree = parser.parse(code, undefined, PARSE_OPTIONS);
|
|
598
|
+
const imports = [];
|
|
599
|
+
|
|
600
|
+
traverseTree(tree.rootNode, (node) => {
|
|
601
|
+
if (node.type === 'import_declaration') {
|
|
602
|
+
const line = node.startPosition.row + 1;
|
|
603
|
+
let modulePath = null;
|
|
604
|
+
let isStatic = node.text.includes('import static');
|
|
605
|
+
let isWildcard = false;
|
|
606
|
+
|
|
607
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
608
|
+
const child = node.namedChild(i);
|
|
609
|
+
if (child.type === 'scoped_identifier' || child.type === 'identifier') {
|
|
610
|
+
modulePath = child.text;
|
|
611
|
+
} else if (child.type === 'asterisk') {
|
|
612
|
+
isWildcard = true;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (modulePath) {
|
|
617
|
+
const segments = modulePath.split('.');
|
|
618
|
+
const name = isWildcard ? '*' : segments[segments.length - 1];
|
|
619
|
+
imports.push({
|
|
620
|
+
module: modulePath + (isWildcard ? '.*' : ''),
|
|
621
|
+
names: [name],
|
|
622
|
+
type: isStatic ? 'static' : 'import',
|
|
623
|
+
line
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
return true;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return true;
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
return imports;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Find all exports in Java code using tree-sitter AST
|
|
637
|
+
* In Java, public classes/interfaces/enums are exports
|
|
638
|
+
* @param {string} code - Source code to analyze
|
|
639
|
+
* @param {object} parser - Tree-sitter parser instance
|
|
640
|
+
* @returns {Array<{name: string, type: string, line: number}>}
|
|
641
|
+
*/
|
|
642
|
+
function findExportsInCode(code, parser) {
|
|
643
|
+
const tree = parser.parse(code, undefined, PARSE_OPTIONS);
|
|
644
|
+
const exports = [];
|
|
645
|
+
|
|
646
|
+
function isPublic(node) {
|
|
647
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
648
|
+
const child = node.namedChild(i);
|
|
649
|
+
if (child.type === 'modifiers' && child.text.includes('public')) {
|
|
650
|
+
return true;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
traverseTree(tree.rootNode, (node) => {
|
|
657
|
+
// Public classes
|
|
658
|
+
if (node.type === 'class_declaration' && isPublic(node)) {
|
|
659
|
+
const nameNode = node.childForFieldName('name');
|
|
660
|
+
if (nameNode) {
|
|
661
|
+
exports.push({
|
|
662
|
+
name: nameNode.text,
|
|
663
|
+
type: 'class',
|
|
664
|
+
line: node.startPosition.row + 1
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
return false; // Don't descend into class body
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Public interfaces
|
|
671
|
+
if (node.type === 'interface_declaration' && isPublic(node)) {
|
|
672
|
+
const nameNode = node.childForFieldName('name');
|
|
673
|
+
if (nameNode) {
|
|
674
|
+
exports.push({
|
|
675
|
+
name: nameNode.text,
|
|
676
|
+
type: 'interface',
|
|
677
|
+
line: node.startPosition.row + 1
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Public enums
|
|
684
|
+
if (node.type === 'enum_declaration' && isPublic(node)) {
|
|
685
|
+
const nameNode = node.childForFieldName('name');
|
|
686
|
+
if (nameNode) {
|
|
687
|
+
exports.push({
|
|
688
|
+
name: nameNode.text,
|
|
689
|
+
type: 'enum',
|
|
690
|
+
line: node.startPosition.row + 1
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// Public records (Java 14+)
|
|
697
|
+
if (node.type === 'record_declaration' && isPublic(node)) {
|
|
698
|
+
const nameNode = node.childForFieldName('name');
|
|
699
|
+
if (nameNode) {
|
|
700
|
+
exports.push({
|
|
701
|
+
name: nameNode.text,
|
|
702
|
+
type: 'record',
|
|
703
|
+
line: node.startPosition.row + 1
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
return false;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
return true;
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
return exports;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Find all usages of a name in code using AST
|
|
717
|
+
* @param {string} code - Source code
|
|
718
|
+
* @param {string} name - Symbol name to find
|
|
719
|
+
* @param {object} parser - Tree-sitter parser instance
|
|
720
|
+
* @returns {Array<{line: number, column: number, usageType: string}>}
|
|
721
|
+
*/
|
|
722
|
+
function findUsagesInCode(code, name, parser) {
|
|
723
|
+
const tree = parser.parse(code, undefined, PARSE_OPTIONS);
|
|
724
|
+
const usages = [];
|
|
725
|
+
|
|
726
|
+
traverseTree(tree.rootNode, (node) => {
|
|
727
|
+
// Only look for identifiers with the matching name
|
|
728
|
+
if (node.type !== 'identifier' || node.text !== name) {
|
|
729
|
+
return true;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
const line = node.startPosition.row + 1;
|
|
733
|
+
const column = node.startPosition.column;
|
|
734
|
+
const parent = node.parent;
|
|
735
|
+
|
|
736
|
+
let usageType = 'reference';
|
|
737
|
+
|
|
738
|
+
if (parent) {
|
|
739
|
+
// Import: part of import declaration
|
|
740
|
+
if (parent.type === 'scoped_identifier' ||
|
|
741
|
+
parent.type === 'import_declaration') {
|
|
742
|
+
// Check if we're inside an import
|
|
743
|
+
let n = parent;
|
|
744
|
+
while (n) {
|
|
745
|
+
if (n.type === 'import_declaration') {
|
|
746
|
+
usageType = 'import';
|
|
747
|
+
break;
|
|
748
|
+
}
|
|
749
|
+
n = n.parent;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
// Call: method_invocation with name field
|
|
753
|
+
else if (parent.type === 'method_invocation' &&
|
|
754
|
+
parent.childForFieldName('name') === node) {
|
|
755
|
+
usageType = 'call';
|
|
756
|
+
}
|
|
757
|
+
// Definition: method name
|
|
758
|
+
else if (parent.type === 'method_declaration' &&
|
|
759
|
+
parent.childForFieldName('name') === node) {
|
|
760
|
+
usageType = 'definition';
|
|
761
|
+
}
|
|
762
|
+
// Definition: class name
|
|
763
|
+
else if (parent.type === 'class_declaration' &&
|
|
764
|
+
parent.childForFieldName('name') === node) {
|
|
765
|
+
usageType = 'definition';
|
|
766
|
+
}
|
|
767
|
+
// Definition: interface name
|
|
768
|
+
else if (parent.type === 'interface_declaration' &&
|
|
769
|
+
parent.childForFieldName('name') === node) {
|
|
770
|
+
usageType = 'definition';
|
|
771
|
+
}
|
|
772
|
+
// Definition: enum name
|
|
773
|
+
else if (parent.type === 'enum_declaration' &&
|
|
774
|
+
parent.childForFieldName('name') === node) {
|
|
775
|
+
usageType = 'definition';
|
|
776
|
+
}
|
|
777
|
+
// Definition: constructor
|
|
778
|
+
else if (parent.type === 'constructor_declaration' &&
|
|
779
|
+
parent.childForFieldName('name') === node) {
|
|
780
|
+
usageType = 'definition';
|
|
781
|
+
}
|
|
782
|
+
// Definition: local variable
|
|
783
|
+
else if (parent.type === 'variable_declarator' &&
|
|
784
|
+
parent.childForFieldName('name') === node) {
|
|
785
|
+
usageType = 'definition';
|
|
786
|
+
}
|
|
787
|
+
// Definition: parameter
|
|
788
|
+
else if (parent.type === 'formal_parameter' ||
|
|
789
|
+
parent.type === 'spread_parameter') {
|
|
790
|
+
usageType = 'definition';
|
|
791
|
+
}
|
|
792
|
+
// Definition: field
|
|
793
|
+
else if (parent.type === 'field_declaration') {
|
|
794
|
+
usageType = 'definition';
|
|
795
|
+
}
|
|
796
|
+
// Object creation: new ClassName()
|
|
797
|
+
else if (parent.type === 'object_creation_expression') {
|
|
798
|
+
const typeNode = parent.childForFieldName('type');
|
|
799
|
+
if (typeNode?.text === name) {
|
|
800
|
+
usageType = 'call';
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
// Field access: obj.field
|
|
804
|
+
else if (parent.type === 'field_access' &&
|
|
805
|
+
parent.childForFieldName('field') === node) {
|
|
806
|
+
usageType = 'reference';
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
usages.push({ line, column, usageType });
|
|
811
|
+
return true;
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
return usages;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
module.exports = {
|
|
818
|
+
findFunctions,
|
|
819
|
+
findClasses,
|
|
820
|
+
findStateObjects,
|
|
821
|
+
findCallsInCode,
|
|
822
|
+
findImportsInCode,
|
|
823
|
+
findExportsInCode,
|
|
824
|
+
findUsagesInCode,
|
|
825
|
+
parse
|
|
826
|
+
};
|