ucn 3.8.7 → 3.8.9
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/cli/index.js +2 -2
- package/core/entrypoints.js +71 -35
- package/core/execute.js +1 -0
- package/core/output.js +1 -0
- package/languages/go.js +50 -2
- package/package.json +1 -1
package/cli/index.js
CHANGED
|
@@ -760,7 +760,7 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
760
760
|
}
|
|
761
761
|
|
|
762
762
|
case 'entrypoints': {
|
|
763
|
-
const { ok, result, error } = execute(index, 'entrypoints', { type: flags.type, framework: flags.framework, file: flags.file });
|
|
763
|
+
const { ok, result, error } = execute(index, 'entrypoints', { type: flags.type, framework: flags.framework, file: flags.file, exclude: flags.exclude });
|
|
764
764
|
if (!ok) fail(error);
|
|
765
765
|
printOutput(result,
|
|
766
766
|
output.formatEntrypointsJson,
|
|
@@ -1389,7 +1389,7 @@ function executeInteractiveCommand(index, command, arg, iflags = {}, cache = nul
|
|
|
1389
1389
|
}
|
|
1390
1390
|
|
|
1391
1391
|
case 'entrypoints': {
|
|
1392
|
-
const { ok, result, error } = execute(index, 'entrypoints', { type: iflags.type, framework: iflags.framework, file: iflags.file });
|
|
1392
|
+
const { ok, result, error } = execute(index, 'entrypoints', { type: iflags.type, framework: iflags.framework, file: iflags.file, exclude: iflags.exclude });
|
|
1393
1393
|
if (!ok) { console.log(error); return; }
|
|
1394
1394
|
console.log(output.formatEntrypoints(result));
|
|
1395
1395
|
break;
|
package/core/entrypoints.js
CHANGED
|
@@ -209,15 +209,15 @@ const FRAMEWORK_PATTERNS = [
|
|
|
209
209
|
// ── Go Framework Patterns ─────────────────────────────────────────
|
|
210
210
|
|
|
211
211
|
// Cobra CLI framework — RunE, Run, PreRunE etc. assigned to cobra.Command struct fields
|
|
212
|
-
// Detected via
|
|
212
|
+
// Detected via composite literal: &cobra.Command{RunE: handler}
|
|
213
213
|
{
|
|
214
214
|
id: 'cobra-command',
|
|
215
215
|
languages: new Set(['go']),
|
|
216
216
|
type: 'cli',
|
|
217
217
|
framework: 'cobra',
|
|
218
|
-
detection: '
|
|
219
|
-
|
|
220
|
-
|
|
218
|
+
detection: 'compositePattern',
|
|
219
|
+
typePattern: /^cobra\.Command$/,
|
|
220
|
+
fieldPattern: /^(Run|RunE|PreRun|PreRunE|PostRun|PostRunE|PersistentPreRun|PersistentPreRunE|PersistentPostRun|PersistentPostRunE)$/,
|
|
221
221
|
},
|
|
222
222
|
|
|
223
223
|
// Go goroutine launch — go func() or go handler()
|
|
@@ -289,50 +289,77 @@ function matchDecoratorOrModifier(symbol, language) {
|
|
|
289
289
|
*/
|
|
290
290
|
function buildCallbackEntrypointMap(index) {
|
|
291
291
|
const callPatterns = FRAMEWORK_PATTERNS.filter(p => p.detection === 'callPattern');
|
|
292
|
-
|
|
292
|
+
const compositePatterns = FRAMEWORK_PATTERNS.filter(p => p.detection === 'compositePattern');
|
|
293
|
+
if (callPatterns.length === 0 && compositePatterns.length === 0) return new Map();
|
|
293
294
|
|
|
294
295
|
const result = new Map(); // name -> info
|
|
295
296
|
|
|
296
297
|
for (const [filePath, fileEntry] of index.files) {
|
|
297
298
|
const lang = fileEntry.language;
|
|
298
|
-
const relevantPatterns = callPatterns.filter(p => p.languages.has(lang));
|
|
299
|
-
if (relevantPatterns.length === 0) continue;
|
|
300
299
|
|
|
301
300
|
const calls = getCachedCalls(index, filePath);
|
|
302
301
|
if (!calls) continue;
|
|
303
302
|
|
|
304
|
-
// Pass 1:
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
for (const
|
|
310
|
-
if (
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
303
|
+
// Pass 1+2: call-pattern detection (e.g., app.GET("/", handler))
|
|
304
|
+
const relevantCallPatterns = callPatterns.filter(p => p.languages.has(lang));
|
|
305
|
+
if (relevantCallPatterns.length > 0) {
|
|
306
|
+
// Pass 1: find route-registration calls, index by line
|
|
307
|
+
const routeLines = new Map(); // line -> { pattern, call }
|
|
308
|
+
for (const call of calls) {
|
|
309
|
+
if (!call.receiver) continue;
|
|
310
|
+
for (const pattern of relevantCallPatterns) {
|
|
311
|
+
if (pattern.receiverPattern.test(call.receiver) &&
|
|
312
|
+
pattern.methodPattern.test(call.name)) {
|
|
313
|
+
routeLines.set(call.line, { pattern, call });
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (routeLines.size > 0) {
|
|
320
|
+
// Pass 2: find callbacks on route-registration lines
|
|
321
|
+
for (const call of calls) {
|
|
322
|
+
if (!call.isFunctionReference && !call.isPotentialCallback) continue;
|
|
323
|
+
const route = routeLines.get(call.line);
|
|
324
|
+
if (!route) continue;
|
|
325
|
+
|
|
326
|
+
if (!result.has(call.name)) {
|
|
327
|
+
result.set(call.name, {
|
|
328
|
+
framework: route.pattern.framework,
|
|
329
|
+
type: route.pattern.type,
|
|
330
|
+
patternId: route.pattern.id,
|
|
331
|
+
method: route.call.name.toUpperCase(),
|
|
332
|
+
file: filePath,
|
|
333
|
+
line: call.line,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
314
336
|
}
|
|
315
337
|
}
|
|
316
338
|
}
|
|
317
339
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
340
|
+
// Pass 3: composite literal patterns (e.g., &cobra.Command{RunE: handler})
|
|
341
|
+
const relevantCompositePatterns = compositePatterns.filter(p => p.languages.has(lang));
|
|
342
|
+
if (relevantCompositePatterns.length > 0) {
|
|
343
|
+
for (const call of calls) {
|
|
344
|
+
if (!call.compositeType) continue;
|
|
345
|
+
if (!call.isPotentialCallback && !call.isFunctionReference) continue;
|
|
346
|
+
|
|
347
|
+
for (const pattern of relevantCompositePatterns) {
|
|
348
|
+
if (pattern.typePattern.test(call.compositeType) &&
|
|
349
|
+
pattern.fieldPattern.test(call.fieldName)) {
|
|
350
|
+
if (!result.has(call.name)) {
|
|
351
|
+
result.set(call.name, {
|
|
352
|
+
framework: pattern.framework,
|
|
353
|
+
type: pattern.type,
|
|
354
|
+
patternId: pattern.id,
|
|
355
|
+
method: call.fieldName,
|
|
356
|
+
file: filePath,
|
|
357
|
+
line: call.line,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
336
363
|
}
|
|
337
364
|
}
|
|
338
365
|
}
|
|
@@ -449,6 +476,15 @@ function detectEntrypoints(index, options = {}) {
|
|
|
449
476
|
filtered = filtered.filter(e => e.file.includes(options.file));
|
|
450
477
|
}
|
|
451
478
|
|
|
479
|
+
if (options.exclude) {
|
|
480
|
+
const raw = Array.isArray(options.exclude) ? options.exclude : options.exclude.split(',');
|
|
481
|
+
const patterns = raw.map(s => s.trim()).filter(Boolean);
|
|
482
|
+
if (patterns.length > 0) {
|
|
483
|
+
const regexes = patterns.map(p => new RegExp(`(^|[/._-])${p}s?([/._-]|$)`, 'i'));
|
|
484
|
+
filtered = filtered.filter(e => !regexes.some(r => r.test(e.file)));
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
452
488
|
// Sort by file, then line
|
|
453
489
|
filtered.sort((a, b) => {
|
|
454
490
|
if (a.file !== b.file) return a.file.localeCompare(b.file);
|
package/core/execute.js
CHANGED
package/core/output.js
CHANGED
package/languages/go.js
CHANGED
|
@@ -954,6 +954,31 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
954
954
|
valueNode = valueNode.namedChildCount > 0 ? valueNode.namedChild(0) : null;
|
|
955
955
|
}
|
|
956
956
|
if (valueNode) {
|
|
957
|
+
// Extract field name (the key) and parent composite literal type
|
|
958
|
+
let keyNode = node.namedChildCount >= 1 ? node.namedChild(0) : null;
|
|
959
|
+
if (keyNode && keyNode.type === 'literal_element') {
|
|
960
|
+
keyNode = keyNode.namedChildCount > 0 ? keyNode.namedChild(0) : null;
|
|
961
|
+
}
|
|
962
|
+
const fieldName = keyNode ? keyNode.text : undefined;
|
|
963
|
+
|
|
964
|
+
let compositeType;
|
|
965
|
+
let compositeLit = node.parent; // literal_value
|
|
966
|
+
if (compositeLit && compositeLit.type === 'literal_value') {
|
|
967
|
+
compositeLit = compositeLit.parent; // composite_literal
|
|
968
|
+
}
|
|
969
|
+
if (compositeLit && compositeLit.type === 'composite_literal') {
|
|
970
|
+
const typeNode = compositeLit.childForFieldName('type');
|
|
971
|
+
if (typeNode) {
|
|
972
|
+
if (typeNode.type === 'qualified_type') {
|
|
973
|
+
const pkg = typeNode.childForFieldName('package')?.text;
|
|
974
|
+
const typeName = typeNode.childForFieldName('name')?.text;
|
|
975
|
+
compositeType = pkg && typeName ? `${pkg}.${typeName}` : typeNode.text;
|
|
976
|
+
} else if (typeNode.type === 'type_identifier') {
|
|
977
|
+
compositeType = typeNode.text;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
957
982
|
if (valueNode.type === 'identifier') {
|
|
958
983
|
const name = valueNode.text;
|
|
959
984
|
if (!GO_SKIP_IDENTS.has(name) && !GO_BUILTINS.has(name) && !importAliases.has(name) && /^[a-zA-Z]/.test(name)) {
|
|
@@ -965,7 +990,9 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
965
990
|
isFunctionReference: true,
|
|
966
991
|
isPotentialCallback: true,
|
|
967
992
|
enclosingFunction,
|
|
968
|
-
uncertain: false
|
|
993
|
+
uncertain: false,
|
|
994
|
+
...(compositeType && { compositeType }),
|
|
995
|
+
...(fieldName && { fieldName }),
|
|
969
996
|
});
|
|
970
997
|
}
|
|
971
998
|
}
|
|
@@ -984,7 +1011,28 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
984
1011
|
...(receiverType && { receiverType }),
|
|
985
1012
|
enclosingFunction,
|
|
986
1013
|
isPotentialCallback: true,
|
|
987
|
-
uncertain: false
|
|
1014
|
+
uncertain: false,
|
|
1015
|
+
...(compositeType && { compositeType }),
|
|
1016
|
+
...(fieldName && { fieldName }),
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
// Inline closure: RunE: func(cmd *cobra.Command, args []string) { ... }
|
|
1021
|
+
// Mark the enclosing function as the entry point (the closure itself has no name)
|
|
1022
|
+
if (valueNode.type === 'func_literal' && compositeType && fieldName) {
|
|
1023
|
+
const enclosing = getCurrentEnclosingFunction();
|
|
1024
|
+
const enclosingName = typeof enclosing === 'string' ? enclosing : enclosing?.name;
|
|
1025
|
+
if (enclosingName) {
|
|
1026
|
+
calls.push({
|
|
1027
|
+
name: enclosingName,
|
|
1028
|
+
line: valueNode.startPosition.row + 1,
|
|
1029
|
+
isMethod: false,
|
|
1030
|
+
isFunctionReference: false,
|
|
1031
|
+
isPotentialCallback: true,
|
|
1032
|
+
enclosingFunction: enclosing,
|
|
1033
|
+
uncertain: false,
|
|
1034
|
+
compositeType,
|
|
1035
|
+
fieldName,
|
|
988
1036
|
});
|
|
989
1037
|
}
|
|
990
1038
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ucn",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.9",
|
|
4
4
|
"mcpName": "io.github.mleoca/ucn",
|
|
5
5
|
"description": "Code intelligence toolkit for AI agents — extract functions, trace call chains, find callers, detect dead code without reading entire files. Works as MCP server, CLI, or agent skill. Supports JS/TS, Python, Go, Rust, Java.",
|
|
6
6
|
"main": "index.js",
|