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
package/index.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UCN - Universal Code Navigator
|
|
3
|
+
*
|
|
4
|
+
* Code navigation built by AI, for AI.
|
|
5
|
+
* Reduces context usage by 90%+ when working with large codebases.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const parser = require('./core/parser');
|
|
9
|
+
const { ProjectIndex } = require('./core/project');
|
|
10
|
+
const discovery = require('./core/discovery');
|
|
11
|
+
const imports = require('./core/imports');
|
|
12
|
+
const output = require('./core/output');
|
|
13
|
+
const languages = require('./languages');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Main API
|
|
17
|
+
*/
|
|
18
|
+
module.exports = {
|
|
19
|
+
// Core parser functions
|
|
20
|
+
parse: parser.parse,
|
|
21
|
+
parseFile: parser.parseFile,
|
|
22
|
+
extractFunction: parser.extractFunction,
|
|
23
|
+
extractClass: parser.extractClass,
|
|
24
|
+
getToc: parser.getToc,
|
|
25
|
+
findSymbol: parser.findSymbol,
|
|
26
|
+
getExportedSymbols: parser.getExportedSymbols,
|
|
27
|
+
|
|
28
|
+
// Language detection
|
|
29
|
+
detectLanguage: parser.detectLanguage,
|
|
30
|
+
isSupported: parser.isSupported,
|
|
31
|
+
|
|
32
|
+
// Project-level operations
|
|
33
|
+
ProjectIndex,
|
|
34
|
+
|
|
35
|
+
// File discovery
|
|
36
|
+
expandGlob: discovery.expandGlob,
|
|
37
|
+
findProjectRoot: discovery.findProjectRoot,
|
|
38
|
+
detectProjectPattern: discovery.detectProjectPattern,
|
|
39
|
+
isTestFile: discovery.isTestFile,
|
|
40
|
+
findTestFileFor: discovery.findTestFileFor,
|
|
41
|
+
|
|
42
|
+
// Import/export analysis
|
|
43
|
+
extractImports: imports.extractImports,
|
|
44
|
+
extractExports: imports.extractExports,
|
|
45
|
+
resolveImport: imports.resolveImport,
|
|
46
|
+
|
|
47
|
+
// Output formatting
|
|
48
|
+
output,
|
|
49
|
+
|
|
50
|
+
// Language modules (for advanced use)
|
|
51
|
+
languages
|
|
52
|
+
};
|
package/languages/go.js
ADDED
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* languages/go.js - Tree-sitter based Go parsing
|
|
3
|
+
*
|
|
4
|
+
* Handles: function declarations, method declarations (with receivers),
|
|
5
|
+
* struct/interface types, and const/var declarations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
traverseTree,
|
|
10
|
+
nodeToLocation,
|
|
11
|
+
parseStructuredParams,
|
|
12
|
+
extractGoDocstring
|
|
13
|
+
} = require('./utils');
|
|
14
|
+
const { PARSE_OPTIONS } = require('./index');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Extract return type from Go function/method
|
|
18
|
+
*/
|
|
19
|
+
function extractReturnType(node) {
|
|
20
|
+
const resultNode = node.childForFieldName('result');
|
|
21
|
+
if (resultNode) {
|
|
22
|
+
return resultNode.text.trim() || null;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Extract Go parameters
|
|
29
|
+
*/
|
|
30
|
+
function extractGoParams(paramsNode) {
|
|
31
|
+
if (!paramsNode) return '...';
|
|
32
|
+
const text = paramsNode.text;
|
|
33
|
+
let params = text.replace(/^\(|\)$/g, '').trim();
|
|
34
|
+
if (!params) return '...';
|
|
35
|
+
return params;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Extract receiver from method declaration
|
|
40
|
+
*/
|
|
41
|
+
function extractReceiver(receiverNode) {
|
|
42
|
+
if (!receiverNode) return null;
|
|
43
|
+
const text = receiverNode.text;
|
|
44
|
+
const match = text.match(/\(\s*\w*\s*(\*?\w+)\s*\)/);
|
|
45
|
+
return match ? match[1] : text.replace(/^\(|\)$/g, '').trim();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Find all functions in Go code using tree-sitter
|
|
50
|
+
*/
|
|
51
|
+
function findFunctions(code, parser) {
|
|
52
|
+
const tree = parser.parse(code, undefined, PARSE_OPTIONS);
|
|
53
|
+
const functions = [];
|
|
54
|
+
const processedRanges = new Set();
|
|
55
|
+
|
|
56
|
+
traverseTree(tree.rootNode, (node) => {
|
|
57
|
+
const rangeKey = `${node.startIndex}-${node.endIndex}`;
|
|
58
|
+
|
|
59
|
+
// Function declarations
|
|
60
|
+
if (node.type === 'function_declaration') {
|
|
61
|
+
if (processedRanges.has(rangeKey)) return true;
|
|
62
|
+
processedRanges.add(rangeKey);
|
|
63
|
+
|
|
64
|
+
const nameNode = node.childForFieldName('name');
|
|
65
|
+
const paramsNode = node.childForFieldName('parameters');
|
|
66
|
+
|
|
67
|
+
if (nameNode) {
|
|
68
|
+
const { startLine, endLine, indent } = nodeToLocation(node, code);
|
|
69
|
+
const returnType = extractReturnType(node);
|
|
70
|
+
const docstring = extractGoDocstring(code, startLine);
|
|
71
|
+
const typeParams = extractTypeParams(node);
|
|
72
|
+
|
|
73
|
+
// Check if exported (capitalized)
|
|
74
|
+
const isExported = /^[A-Z]/.test(nameNode.text);
|
|
75
|
+
|
|
76
|
+
functions.push({
|
|
77
|
+
name: nameNode.text,
|
|
78
|
+
params: extractGoParams(paramsNode),
|
|
79
|
+
paramsStructured: parseStructuredParams(paramsNode, 'go'),
|
|
80
|
+
startLine,
|
|
81
|
+
endLine,
|
|
82
|
+
indent,
|
|
83
|
+
modifiers: isExported ? ['export'] : [],
|
|
84
|
+
...(returnType && { returnType }),
|
|
85
|
+
...(docstring && { docstring }),
|
|
86
|
+
...(typeParams && { generics: typeParams })
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Method declarations (with receivers)
|
|
93
|
+
if (node.type === 'method_declaration') {
|
|
94
|
+
if (processedRanges.has(rangeKey)) return true;
|
|
95
|
+
processedRanges.add(rangeKey);
|
|
96
|
+
|
|
97
|
+
const nameNode = node.childForFieldName('name');
|
|
98
|
+
const paramsNode = node.childForFieldName('parameters');
|
|
99
|
+
const receiverNode = node.childForFieldName('receiver');
|
|
100
|
+
|
|
101
|
+
if (nameNode) {
|
|
102
|
+
const { startLine, endLine, indent } = nodeToLocation(node, code);
|
|
103
|
+
const receiver = extractReceiver(receiverNode);
|
|
104
|
+
const returnType = extractReturnType(node);
|
|
105
|
+
const docstring = extractGoDocstring(code, startLine);
|
|
106
|
+
|
|
107
|
+
// Check if exported
|
|
108
|
+
const isExported = /^[A-Z]/.test(nameNode.text);
|
|
109
|
+
|
|
110
|
+
functions.push({
|
|
111
|
+
name: nameNode.text,
|
|
112
|
+
params: extractGoParams(paramsNode),
|
|
113
|
+
paramsStructured: parseStructuredParams(paramsNode, 'go'),
|
|
114
|
+
startLine,
|
|
115
|
+
endLine,
|
|
116
|
+
indent,
|
|
117
|
+
isMethod: true,
|
|
118
|
+
receiver,
|
|
119
|
+
modifiers: isExported ? ['export'] : [],
|
|
120
|
+
...(returnType && { returnType }),
|
|
121
|
+
...(docstring && { docstring })
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return true;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
functions.sort((a, b) => a.startLine - b.startLine);
|
|
131
|
+
return functions;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Extract type parameters (generics) from function/type
|
|
136
|
+
*/
|
|
137
|
+
function extractTypeParams(node) {
|
|
138
|
+
const typeParamsNode = node.childForFieldName('type_parameters');
|
|
139
|
+
if (typeParamsNode) {
|
|
140
|
+
return typeParamsNode.text;
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Find all types (structs, interfaces) in Go code using tree-sitter
|
|
147
|
+
*/
|
|
148
|
+
function findClasses(code, parser) {
|
|
149
|
+
const tree = parser.parse(code, undefined, PARSE_OPTIONS);
|
|
150
|
+
const types = [];
|
|
151
|
+
const processedRanges = new Set();
|
|
152
|
+
|
|
153
|
+
traverseTree(tree.rootNode, (node) => {
|
|
154
|
+
const rangeKey = `${node.startIndex}-${node.endIndex}`;
|
|
155
|
+
|
|
156
|
+
if (node.type === 'type_declaration') {
|
|
157
|
+
if (processedRanges.has(rangeKey)) return true;
|
|
158
|
+
processedRanges.add(rangeKey);
|
|
159
|
+
|
|
160
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
161
|
+
const spec = node.namedChild(i);
|
|
162
|
+
if (spec.type === 'type_spec') {
|
|
163
|
+
const nameNode = spec.childForFieldName('name');
|
|
164
|
+
const typeNode = spec.childForFieldName('type');
|
|
165
|
+
|
|
166
|
+
if (nameNode && typeNode) {
|
|
167
|
+
const { startLine, endLine } = nodeToLocation(node, code);
|
|
168
|
+
const name = nameNode.text;
|
|
169
|
+
const docstring = extractGoDocstring(code, startLine);
|
|
170
|
+
const typeParams = extractTypeParams(spec);
|
|
171
|
+
|
|
172
|
+
let typeKind = 'type';
|
|
173
|
+
if (typeNode.type === 'struct_type') {
|
|
174
|
+
typeKind = 'struct';
|
|
175
|
+
} else if (typeNode.type === 'interface_type') {
|
|
176
|
+
typeKind = 'interface';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Check if exported
|
|
180
|
+
const isExported = /^[A-Z]/.test(name);
|
|
181
|
+
|
|
182
|
+
types.push({
|
|
183
|
+
name,
|
|
184
|
+
startLine,
|
|
185
|
+
endLine,
|
|
186
|
+
type: typeKind,
|
|
187
|
+
members: typeKind === 'struct' ? extractStructFields(typeNode, code) : [],
|
|
188
|
+
modifiers: isExported ? ['export'] : [],
|
|
189
|
+
...(docstring && { docstring }),
|
|
190
|
+
...(typeParams && { generics: typeParams })
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return true;
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
types.sort((a, b) => a.startLine - b.startLine);
|
|
202
|
+
return types;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Extract struct fields
|
|
207
|
+
*/
|
|
208
|
+
function extractStructFields(structNode, code) {
|
|
209
|
+
const fields = [];
|
|
210
|
+
const fieldListNode = structNode.childForFieldName('body') || structNode;
|
|
211
|
+
|
|
212
|
+
for (let i = 0; i < fieldListNode.namedChildCount; i++) {
|
|
213
|
+
const field = fieldListNode.namedChild(i);
|
|
214
|
+
if (field.type === 'field_declaration') {
|
|
215
|
+
const { startLine, endLine } = nodeToLocation(field, code);
|
|
216
|
+
const nameNode = field.childForFieldName('name');
|
|
217
|
+
const typeNode = field.childForFieldName('type');
|
|
218
|
+
|
|
219
|
+
if (nameNode) {
|
|
220
|
+
fields.push({
|
|
221
|
+
name: nameNode.text,
|
|
222
|
+
startLine,
|
|
223
|
+
endLine,
|
|
224
|
+
memberType: 'field',
|
|
225
|
+
...(typeNode && { fieldType: typeNode.text })
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return fields;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Find state objects (constants) in Go code
|
|
236
|
+
*/
|
|
237
|
+
function findStateObjects(code, parser) {
|
|
238
|
+
const tree = parser.parse(code, undefined, PARSE_OPTIONS);
|
|
239
|
+
const objects = [];
|
|
240
|
+
|
|
241
|
+
const statePattern = /^(CONFIG|SETTINGS|[A-Z][A-Z0-9_]+|Default[A-Z][a-zA-Z]*|[A-Z][a-zA-Z]*(?:Config|Settings|Options))$/;
|
|
242
|
+
|
|
243
|
+
// Check if a value node is a composite literal
|
|
244
|
+
function isCompositeLiteral(valueNode) {
|
|
245
|
+
if (!valueNode) return false;
|
|
246
|
+
if (valueNode.type === 'composite_literal') return true;
|
|
247
|
+
for (let i = 0; i < valueNode.namedChildCount; i++) {
|
|
248
|
+
if (valueNode.namedChild(i).type === 'composite_literal') return true;
|
|
249
|
+
}
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
traverseTree(tree.rootNode, (node) => {
|
|
254
|
+
// Handle const declarations
|
|
255
|
+
if (node.type === 'const_declaration') {
|
|
256
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
257
|
+
const spec = node.namedChild(i);
|
|
258
|
+
if (spec.type === 'const_spec') {
|
|
259
|
+
const nameNode = spec.childForFieldName('name');
|
|
260
|
+
const valueNode = spec.childForFieldName('value');
|
|
261
|
+
|
|
262
|
+
if (nameNode && valueNode && isCompositeLiteral(valueNode)) {
|
|
263
|
+
const name = nameNode.text;
|
|
264
|
+
if (statePattern.test(name)) {
|
|
265
|
+
const { startLine, endLine } = nodeToLocation(spec, code);
|
|
266
|
+
objects.push({ name, startLine, endLine });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Handle var declarations
|
|
275
|
+
if (node.type === 'var_declaration') {
|
|
276
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
277
|
+
const spec = node.namedChild(i);
|
|
278
|
+
if (spec.type === 'var_spec') {
|
|
279
|
+
const nameNode = spec.childForFieldName('name');
|
|
280
|
+
const valueNode = spec.childForFieldName('value');
|
|
281
|
+
|
|
282
|
+
if (nameNode && valueNode && isCompositeLiteral(valueNode)) {
|
|
283
|
+
const name = nameNode.text;
|
|
284
|
+
if (statePattern.test(name)) {
|
|
285
|
+
const { startLine, endLine } = nodeToLocation(spec, code);
|
|
286
|
+
objects.push({ name, startLine, endLine });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return true;
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
objects.sort((a, b) => a.startLine - b.startLine);
|
|
298
|
+
return objects;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Parse a Go file completely
|
|
303
|
+
*/
|
|
304
|
+
function parse(code, parser) {
|
|
305
|
+
return {
|
|
306
|
+
language: 'go',
|
|
307
|
+
totalLines: code.split('\n').length,
|
|
308
|
+
functions: findFunctions(code, parser),
|
|
309
|
+
classes: findClasses(code, parser),
|
|
310
|
+
stateObjects: findStateObjects(code, parser),
|
|
311
|
+
imports: [],
|
|
312
|
+
exports: []
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Find all function calls in Go code using tree-sitter AST
|
|
318
|
+
* @param {string} code - Source code to analyze
|
|
319
|
+
* @param {object} parser - Tree-sitter parser instance
|
|
320
|
+
* @returns {Array<{name: string, line: number, isMethod: boolean, receiver?: string}>}
|
|
321
|
+
*/
|
|
322
|
+
function findCallsInCode(code, parser) {
|
|
323
|
+
const tree = parser.parse(code, undefined, PARSE_OPTIONS);
|
|
324
|
+
const calls = [];
|
|
325
|
+
const functionStack = []; // Stack of { name, startLine, endLine }
|
|
326
|
+
|
|
327
|
+
// Helper to check if a node creates a function scope
|
|
328
|
+
const isFunctionNode = (node) => {
|
|
329
|
+
return ['function_declaration', 'method_declaration', 'func_literal'].includes(node.type);
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
// Helper to extract function name from a function node
|
|
333
|
+
const extractFunctionName = (node) => {
|
|
334
|
+
if (node.type === 'function_declaration') {
|
|
335
|
+
const nameNode = node.childForFieldName('name');
|
|
336
|
+
return nameNode?.text || '<anonymous>';
|
|
337
|
+
}
|
|
338
|
+
if (node.type === 'method_declaration') {
|
|
339
|
+
const nameNode = node.childForFieldName('name');
|
|
340
|
+
return nameNode?.text || '<anonymous>';
|
|
341
|
+
}
|
|
342
|
+
if (node.type === 'func_literal') {
|
|
343
|
+
return '<anonymous>';
|
|
344
|
+
}
|
|
345
|
+
return '<anonymous>';
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// Helper to get current enclosing function
|
|
349
|
+
const getCurrentEnclosingFunction = () => {
|
|
350
|
+
return functionStack.length > 0
|
|
351
|
+
? { ...functionStack[functionStack.length - 1] }
|
|
352
|
+
: null;
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
traverseTree(tree.rootNode, (node) => {
|
|
356
|
+
// Track function entry
|
|
357
|
+
if (isFunctionNode(node)) {
|
|
358
|
+
functionStack.push({
|
|
359
|
+
name: extractFunctionName(node),
|
|
360
|
+
startLine: node.startPosition.row + 1,
|
|
361
|
+
endLine: node.endPosition.row + 1
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Handle function calls: foo(), pkg.Foo(), obj.Method()
|
|
366
|
+
if (node.type === 'call_expression') {
|
|
367
|
+
const funcNode = node.childForFieldName('function');
|
|
368
|
+
if (!funcNode) return true;
|
|
369
|
+
|
|
370
|
+
const enclosingFunction = getCurrentEnclosingFunction();
|
|
371
|
+
|
|
372
|
+
if (funcNode.type === 'identifier') {
|
|
373
|
+
// Direct call: foo()
|
|
374
|
+
calls.push({
|
|
375
|
+
name: funcNode.text,
|
|
376
|
+
line: node.startPosition.row + 1,
|
|
377
|
+
isMethod: false,
|
|
378
|
+
enclosingFunction
|
|
379
|
+
});
|
|
380
|
+
} else if (funcNode.type === 'selector_expression') {
|
|
381
|
+
// Method or package call: obj.Method() or pkg.Func()
|
|
382
|
+
const fieldNode = funcNode.childForFieldName('field');
|
|
383
|
+
const operandNode = funcNode.childForFieldName('operand');
|
|
384
|
+
|
|
385
|
+
if (fieldNode) {
|
|
386
|
+
calls.push({
|
|
387
|
+
name: fieldNode.text,
|
|
388
|
+
line: node.startPosition.row + 1,
|
|
389
|
+
isMethod: true,
|
|
390
|
+
receiver: operandNode?.type === 'identifier' ? operandNode.text : undefined,
|
|
391
|
+
enclosingFunction
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return true;
|
|
399
|
+
}, {
|
|
400
|
+
onLeave: (node) => {
|
|
401
|
+
if (isFunctionNode(node)) {
|
|
402
|
+
functionStack.pop();
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
return calls;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Find all imports in Go code using tree-sitter AST
|
|
412
|
+
* @param {string} code - Source code to analyze
|
|
413
|
+
* @param {object} parser - Tree-sitter parser instance
|
|
414
|
+
* @returns {Array<{module: string, names: string[], type: string, line: number}>}
|
|
415
|
+
*/
|
|
416
|
+
function findImportsInCode(code, parser) {
|
|
417
|
+
const tree = parser.parse(code, undefined, PARSE_OPTIONS);
|
|
418
|
+
const imports = [];
|
|
419
|
+
|
|
420
|
+
function processImportSpec(spec) {
|
|
421
|
+
const line = spec.startPosition.row + 1;
|
|
422
|
+
let modulePath = null;
|
|
423
|
+
let alias = null;
|
|
424
|
+
let importType = 'import';
|
|
425
|
+
|
|
426
|
+
for (let i = 0; i < spec.namedChildCount; i++) {
|
|
427
|
+
const child = spec.namedChild(i);
|
|
428
|
+
if (child.type === 'interpreted_string_literal') {
|
|
429
|
+
// Remove quotes
|
|
430
|
+
modulePath = child.text.slice(1, -1);
|
|
431
|
+
} else if (child.type === 'package_identifier') {
|
|
432
|
+
alias = child.text;
|
|
433
|
+
} else if (child.type === 'blank_identifier') {
|
|
434
|
+
alias = '_';
|
|
435
|
+
importType = 'side-effect';
|
|
436
|
+
} else if (child.type === 'dot') {
|
|
437
|
+
alias = '.';
|
|
438
|
+
importType = 'dot-import';
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (modulePath) {
|
|
443
|
+
// Package name is last segment of path
|
|
444
|
+
const pkgName = alias || modulePath.split('/').pop();
|
|
445
|
+
imports.push({
|
|
446
|
+
module: modulePath,
|
|
447
|
+
names: [pkgName],
|
|
448
|
+
type: importType,
|
|
449
|
+
line
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
traverseTree(tree.rootNode, (node) => {
|
|
455
|
+
if (node.type === 'import_declaration') {
|
|
456
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
457
|
+
const child = node.namedChild(i);
|
|
458
|
+
if (child.type === 'import_spec') {
|
|
459
|
+
processImportSpec(child);
|
|
460
|
+
} else if (child.type === 'import_spec_list') {
|
|
461
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
462
|
+
const spec = child.namedChild(j);
|
|
463
|
+
if (spec.type === 'import_spec') {
|
|
464
|
+
processImportSpec(spec);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return true;
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
return imports;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Find all exports in Go code using tree-sitter AST
|
|
480
|
+
* In Go, exports are capitalized public symbols
|
|
481
|
+
* @param {string} code - Source code to analyze
|
|
482
|
+
* @param {object} parser - Tree-sitter parser instance
|
|
483
|
+
* @returns {Array<{name: string, type: string, line: number}>}
|
|
484
|
+
*/
|
|
485
|
+
function findExportsInCode(code, parser) {
|
|
486
|
+
const tree = parser.parse(code, undefined, PARSE_OPTIONS);
|
|
487
|
+
const exports = [];
|
|
488
|
+
|
|
489
|
+
traverseTree(tree.rootNode, (node) => {
|
|
490
|
+
// Exported functions
|
|
491
|
+
if (node.type === 'function_declaration') {
|
|
492
|
+
const nameNode = node.childForFieldName('name');
|
|
493
|
+
if (nameNode && /^[A-Z]/.test(nameNode.text)) {
|
|
494
|
+
exports.push({
|
|
495
|
+
name: nameNode.text,
|
|
496
|
+
type: 'function',
|
|
497
|
+
line: node.startPosition.row + 1
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
return true;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Exported methods
|
|
504
|
+
if (node.type === 'method_declaration') {
|
|
505
|
+
const nameNode = node.childForFieldName('name');
|
|
506
|
+
if (nameNode && /^[A-Z]/.test(nameNode.text)) {
|
|
507
|
+
exports.push({
|
|
508
|
+
name: nameNode.text,
|
|
509
|
+
type: 'method',
|
|
510
|
+
line: node.startPosition.row + 1
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Exported types (struct, interface, type alias)
|
|
517
|
+
if (node.type === 'type_declaration') {
|
|
518
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
519
|
+
const spec = node.namedChild(i);
|
|
520
|
+
if (spec.type === 'type_spec') {
|
|
521
|
+
const nameNode = spec.childForFieldName('name');
|
|
522
|
+
if (nameNode && /^[A-Z]/.test(nameNode.text)) {
|
|
523
|
+
exports.push({
|
|
524
|
+
name: nameNode.text,
|
|
525
|
+
type: 'type',
|
|
526
|
+
line: node.startPosition.row + 1
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return true;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Exported const/var
|
|
535
|
+
if (node.type === 'const_declaration' || node.type === 'var_declaration') {
|
|
536
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
537
|
+
const spec = node.namedChild(i);
|
|
538
|
+
if (spec.type === 'const_spec' || spec.type === 'var_spec') {
|
|
539
|
+
const nameNode = spec.childForFieldName('name');
|
|
540
|
+
if (nameNode && /^[A-Z]/.test(nameNode.text)) {
|
|
541
|
+
exports.push({
|
|
542
|
+
name: nameNode.text,
|
|
543
|
+
type: node.type === 'const_declaration' ? 'const' : 'var',
|
|
544
|
+
line: node.startPosition.row + 1
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return true;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return true;
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
return exports;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Find all usages of a name in code using AST
|
|
560
|
+
* @param {string} code - Source code
|
|
561
|
+
* @param {string} name - Symbol name to find
|
|
562
|
+
* @param {object} parser - Tree-sitter parser instance
|
|
563
|
+
* @returns {Array<{line: number, column: number, usageType: string}>}
|
|
564
|
+
*/
|
|
565
|
+
function findUsagesInCode(code, name, parser) {
|
|
566
|
+
const tree = parser.parse(code, undefined, PARSE_OPTIONS);
|
|
567
|
+
const usages = [];
|
|
568
|
+
|
|
569
|
+
traverseTree(tree.rootNode, (node) => {
|
|
570
|
+
// Only look for identifiers with the matching name
|
|
571
|
+
if (node.type !== 'identifier' || node.text !== name) {
|
|
572
|
+
return true;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const line = node.startPosition.row + 1;
|
|
576
|
+
const column = node.startPosition.column;
|
|
577
|
+
const parent = node.parent;
|
|
578
|
+
|
|
579
|
+
let usageType = 'reference';
|
|
580
|
+
|
|
581
|
+
if (parent) {
|
|
582
|
+
// Import: import_spec
|
|
583
|
+
if (parent.type === 'import_spec' ||
|
|
584
|
+
parent.type === 'package_identifier') {
|
|
585
|
+
usageType = 'import';
|
|
586
|
+
}
|
|
587
|
+
// Call: identifier is function in call_expression
|
|
588
|
+
else if (parent.type === 'call_expression' &&
|
|
589
|
+
parent.childForFieldName('function') === node) {
|
|
590
|
+
usageType = 'call';
|
|
591
|
+
}
|
|
592
|
+
// Definition: function name
|
|
593
|
+
else if (parent.type === 'function_declaration' &&
|
|
594
|
+
parent.childForFieldName('name') === node) {
|
|
595
|
+
usageType = 'definition';
|
|
596
|
+
}
|
|
597
|
+
// Definition: method name
|
|
598
|
+
else if (parent.type === 'method_declaration' &&
|
|
599
|
+
parent.childForFieldName('name') === node) {
|
|
600
|
+
usageType = 'definition';
|
|
601
|
+
}
|
|
602
|
+
// Definition: type name
|
|
603
|
+
else if (parent.type === 'type_spec' &&
|
|
604
|
+
parent.childForFieldName('name') === node) {
|
|
605
|
+
usageType = 'definition';
|
|
606
|
+
}
|
|
607
|
+
// Definition: variable name in short var declaration
|
|
608
|
+
else if (parent.type === 'short_var_declaration') {
|
|
609
|
+
const left = parent.childForFieldName('left');
|
|
610
|
+
if (left && (left === node || left.text.includes(name))) {
|
|
611
|
+
usageType = 'definition';
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
// Definition: const/var spec
|
|
615
|
+
else if (parent.type === 'const_spec' || parent.type === 'var_spec') {
|
|
616
|
+
const nameNode = parent.childForFieldName('name');
|
|
617
|
+
if (nameNode === node) {
|
|
618
|
+
usageType = 'definition';
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
// Definition: parameter
|
|
622
|
+
else if (parent.type === 'parameter_declaration') {
|
|
623
|
+
usageType = 'definition';
|
|
624
|
+
}
|
|
625
|
+
// Method call: selector_expression followed by call
|
|
626
|
+
else if (parent.type === 'selector_expression' &&
|
|
627
|
+
parent.childForFieldName('field') === node) {
|
|
628
|
+
const grandparent = parent.parent;
|
|
629
|
+
if (grandparent && grandparent.type === 'call_expression') {
|
|
630
|
+
usageType = 'call';
|
|
631
|
+
} else {
|
|
632
|
+
usageType = 'reference';
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
usages.push({ line, column, usageType });
|
|
638
|
+
return true;
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
return usages;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
module.exports = {
|
|
645
|
+
findFunctions,
|
|
646
|
+
findClasses,
|
|
647
|
+
findStateObjects,
|
|
648
|
+
findCallsInCode,
|
|
649
|
+
findImportsInCode,
|
|
650
|
+
findExportsInCode,
|
|
651
|
+
findUsagesInCode,
|
|
652
|
+
parse
|
|
653
|
+
};
|