ucn 3.0.0 → 3.1.1
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/README.md +13 -6
- package/cli/index.js +66 -4
- package/core/imports.js +83 -0
- package/core/output.js +39 -5
- package/core/project.js +142 -11
- package/languages/java.js +8 -0
- package/languages/javascript.js +3 -0
- package/languages/python.js +15 -0
- package/languages/rust.js +61 -2
- package/package.json +2 -2
- package/test/parser.test.js +690 -1
package/README.md
CHANGED
|
@@ -38,13 +38,8 @@ JavaScript, TypeScript, Python, Go, Rust, Java
|
|
|
38
38
|
|
|
39
39
|
## Install
|
|
40
40
|
|
|
41
|
-
Not published to npm yet. Install from source:
|
|
42
|
-
|
|
43
41
|
```bash
|
|
44
|
-
|
|
45
|
-
cd ucn
|
|
46
|
-
npm install
|
|
47
|
-
npm link # makes 'ucn' available globally
|
|
42
|
+
npm install -g ucn
|
|
48
43
|
```
|
|
49
44
|
|
|
50
45
|
### Claude Code (optional)
|
|
@@ -53,6 +48,12 @@ To use UCN as a skill in Claude Code:
|
|
|
53
48
|
|
|
54
49
|
```bash
|
|
55
50
|
mkdir -p ~/.claude/skills
|
|
51
|
+
|
|
52
|
+
# If installed via npm:
|
|
53
|
+
cp -r "$(npm root -g)/ucn/.claude/skills/ucn" ~/.claude/skills/
|
|
54
|
+
|
|
55
|
+
# If cloned from git:
|
|
56
|
+
git clone https://github.com/mleoca/ucn.git
|
|
56
57
|
cp -r ucn/.claude/skills/ucn ~/.claude/skills/
|
|
57
58
|
```
|
|
58
59
|
|
|
@@ -62,6 +63,12 @@ To use UCN as a skill in OpenAI Codex:
|
|
|
62
63
|
|
|
63
64
|
```bash
|
|
64
65
|
mkdir -p ~/.agents/skills
|
|
66
|
+
|
|
67
|
+
# If installed via npm:
|
|
68
|
+
cp -r "$(npm root -g)/ucn/.claude/skills/ucn" ~/.agents/skills/
|
|
69
|
+
|
|
70
|
+
# If cloned from git:
|
|
71
|
+
git clone https://github.com/mleoca/ucn.git
|
|
65
72
|
cp -r ucn/.claude/skills/ucn ~/.agents/skills/
|
|
66
73
|
```
|
|
67
74
|
|
package/cli/index.js
CHANGED
|
@@ -1550,6 +1550,66 @@ function printBestExample(index, name) {
|
|
|
1550
1550
|
}
|
|
1551
1551
|
|
|
1552
1552
|
function printContext(ctx, options = {}) {
|
|
1553
|
+
// Handle struct/interface types differently
|
|
1554
|
+
if (ctx.type && ['struct', 'interface', 'type'].includes(ctx.type)) {
|
|
1555
|
+
console.log(`Context for ${ctx.type} ${ctx.name}:`);
|
|
1556
|
+
console.log('═'.repeat(60));
|
|
1557
|
+
|
|
1558
|
+
// Display warnings if any
|
|
1559
|
+
if (ctx.warnings && ctx.warnings.length > 0) {
|
|
1560
|
+
console.log('\n⚠️ WARNINGS:');
|
|
1561
|
+
for (const w of ctx.warnings) {
|
|
1562
|
+
console.log(` ${w.message}`);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
const expandable = [];
|
|
1567
|
+
let itemNum = 1;
|
|
1568
|
+
|
|
1569
|
+
// Show methods for structs/interfaces
|
|
1570
|
+
const methods = ctx.methods || [];
|
|
1571
|
+
console.log(`\nMETHODS (${methods.length}):`);
|
|
1572
|
+
for (const m of methods) {
|
|
1573
|
+
const receiver = m.receiver ? `(${m.receiver}) ` : '';
|
|
1574
|
+
const params = m.params || '...';
|
|
1575
|
+
const returnType = m.returnType ? `: ${m.returnType}` : '';
|
|
1576
|
+
console.log(` [${itemNum}] ${receiver}${m.name}(${params})${returnType}`);
|
|
1577
|
+
console.log(` ${m.file}:${m.line}`);
|
|
1578
|
+
expandable.push({
|
|
1579
|
+
num: itemNum++,
|
|
1580
|
+
type: 'method',
|
|
1581
|
+
name: m.name,
|
|
1582
|
+
relativePath: m.file,
|
|
1583
|
+
startLine: m.line
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
// Show callers (type references/usages)
|
|
1588
|
+
const callers = ctx.callers || [];
|
|
1589
|
+
console.log(`\nUSAGES (${callers.length}):`);
|
|
1590
|
+
for (const c of callers) {
|
|
1591
|
+
const callerName = c.callerName || '(module level)';
|
|
1592
|
+
const displayName = c.callerName ? ` [${callerName}]` : '';
|
|
1593
|
+
console.log(` [${itemNum}] ${c.relativePath}:${c.line}${displayName}`);
|
|
1594
|
+
expandable.push({
|
|
1595
|
+
num: itemNum++,
|
|
1596
|
+
type: 'usage',
|
|
1597
|
+
name: callerName,
|
|
1598
|
+
file: c.callerFile || c.file,
|
|
1599
|
+
relativePath: c.relativePath,
|
|
1600
|
+
line: c.line,
|
|
1601
|
+
startLine: c.callerStartLine || c.line,
|
|
1602
|
+
endLine: c.callerEndLine || c.line
|
|
1603
|
+
});
|
|
1604
|
+
console.log(` ${c.content.trim()}`);
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
console.log(`\nUse "ucn . expand <N>" to see code for item N`);
|
|
1608
|
+
lastContextExpandable = expandable;
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
// Standard function/method context
|
|
1553
1613
|
console.log(`Context for ${ctx.function}:`);
|
|
1554
1614
|
console.log('═'.repeat(60));
|
|
1555
1615
|
|
|
@@ -1565,8 +1625,9 @@ function printContext(ctx, options = {}) {
|
|
|
1565
1625
|
const expandable = [];
|
|
1566
1626
|
let itemNum = 1;
|
|
1567
1627
|
|
|
1568
|
-
|
|
1569
|
-
|
|
1628
|
+
const callers = ctx.callers || [];
|
|
1629
|
+
console.log(`\nCALLERS (${callers.length}):`);
|
|
1630
|
+
for (const c of callers) {
|
|
1570
1631
|
// All callers are numbered for expand command
|
|
1571
1632
|
const callerName = c.callerName || '(module level)';
|
|
1572
1633
|
const displayName = c.callerName ? ` [${callerName}]` : '';
|
|
@@ -1584,8 +1645,9 @@ function printContext(ctx, options = {}) {
|
|
|
1584
1645
|
console.log(` ${c.content.trim()}`);
|
|
1585
1646
|
}
|
|
1586
1647
|
|
|
1587
|
-
|
|
1588
|
-
|
|
1648
|
+
const callees = ctx.callees || [];
|
|
1649
|
+
console.log(`\nCALLEES (${callees.length}):`);
|
|
1650
|
+
for (const c of callees) {
|
|
1589
1651
|
const weight = c.weight && c.weight !== 'normal' ? ` [${c.weight}]` : '';
|
|
1590
1652
|
console.log(` [${itemNum}] ${c.name}${weight} - ${c.relativePath}:${c.startLine}`);
|
|
1591
1653
|
expandable.push({
|
package/core/imports.js
CHANGED
|
@@ -431,6 +431,12 @@ function resolveImport(importPath, fromFile, config = {}) {
|
|
|
431
431
|
}
|
|
432
432
|
}
|
|
433
433
|
|
|
434
|
+
// Check Go module imports
|
|
435
|
+
if (config.language === 'go') {
|
|
436
|
+
const resolved = resolveGoImport(importPath, fromFile, config.root);
|
|
437
|
+
if (resolved) return resolved;
|
|
438
|
+
}
|
|
439
|
+
|
|
434
440
|
return null; // External package
|
|
435
441
|
}
|
|
436
442
|
|
|
@@ -439,6 +445,83 @@ function resolveImport(importPath, fromFile, config = {}) {
|
|
|
439
445
|
return resolveFilePath(resolved, config.extensions || getExtensions(config.language));
|
|
440
446
|
}
|
|
441
447
|
|
|
448
|
+
// Cache for Go module paths
|
|
449
|
+
const goModuleCache = new Map();
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Find and parse go.mod to get the module path
|
|
453
|
+
* @param {string} startDir - Directory to start searching from
|
|
454
|
+
* @returns {{modulePath: string, root: string}|null}
|
|
455
|
+
*/
|
|
456
|
+
function findGoModule(startDir) {
|
|
457
|
+
// Check cache first
|
|
458
|
+
if (goModuleCache.has(startDir)) {
|
|
459
|
+
return goModuleCache.get(startDir);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
let dir = startDir;
|
|
463
|
+
while (dir !== path.dirname(dir)) {
|
|
464
|
+
const goModPath = path.join(dir, 'go.mod');
|
|
465
|
+
if (fs.existsSync(goModPath)) {
|
|
466
|
+
try {
|
|
467
|
+
const content = fs.readFileSync(goModPath, 'utf-8');
|
|
468
|
+
// Parse module line: module github.com/user/project
|
|
469
|
+
const match = content.match(/^module\s+(\S+)/m);
|
|
470
|
+
if (match) {
|
|
471
|
+
const result = { modulePath: match[1], root: dir };
|
|
472
|
+
goModuleCache.set(startDir, result);
|
|
473
|
+
return result;
|
|
474
|
+
}
|
|
475
|
+
} catch (e) {
|
|
476
|
+
// Ignore read errors
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
dir = path.dirname(dir);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
goModuleCache.set(startDir, null);
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Resolve Go package import to local files
|
|
488
|
+
* @param {string} importPath - Go import path (e.g., "github.com/user/proj/pkg/util")
|
|
489
|
+
* @param {string} fromFile - File containing the import
|
|
490
|
+
* @param {string} projectRoot - Project root directory
|
|
491
|
+
* @returns {string|null} - Directory path containing the package, or null if external
|
|
492
|
+
*/
|
|
493
|
+
function resolveGoImport(importPath, fromFile, projectRoot) {
|
|
494
|
+
const goMod = findGoModule(path.dirname(fromFile));
|
|
495
|
+
if (!goMod) return null;
|
|
496
|
+
|
|
497
|
+
const { modulePath, root } = goMod;
|
|
498
|
+
|
|
499
|
+
// Check if the import is within this module
|
|
500
|
+
if (importPath.startsWith(modulePath)) {
|
|
501
|
+
// Convert module path to relative path
|
|
502
|
+
// e.g., "github.com/user/proj/pkg/util" -> "pkg/util"
|
|
503
|
+
const relativePath = importPath.slice(modulePath.length).replace(/^\//, '');
|
|
504
|
+
const pkgDir = path.join(root, relativePath);
|
|
505
|
+
|
|
506
|
+
// Go imports are directories, find a .go file in the directory
|
|
507
|
+
if (fs.existsSync(pkgDir) && fs.statSync(pkgDir).isDirectory()) {
|
|
508
|
+
// Return the first .go file in the directory (not _test.go)
|
|
509
|
+
try {
|
|
510
|
+
const files = fs.readdirSync(pkgDir);
|
|
511
|
+
for (const file of files) {
|
|
512
|
+
if (file.endsWith('.go') && !file.endsWith('_test.go')) {
|
|
513
|
+
return path.join(pkgDir, file);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
} catch (e) {
|
|
517
|
+
// Ignore read errors
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
|
|
442
525
|
/**
|
|
443
526
|
* Try to resolve a path with various extensions
|
|
444
527
|
*/
|
package/core/output.js
CHANGED
|
@@ -307,25 +307,59 @@ function formatUsagesJson(usages, name) {
|
|
|
307
307
|
* Format context (callers + callees) as JSON
|
|
308
308
|
*/
|
|
309
309
|
function formatContextJson(context) {
|
|
310
|
+
// Handle struct/interface types differently
|
|
311
|
+
if (context.type && ['struct', 'interface', 'type'].includes(context.type)) {
|
|
312
|
+
const callers = context.callers || [];
|
|
313
|
+
const methods = context.methods || [];
|
|
314
|
+
return JSON.stringify({
|
|
315
|
+
type: context.type,
|
|
316
|
+
name: context.name,
|
|
317
|
+
file: context.file,
|
|
318
|
+
startLine: context.startLine,
|
|
319
|
+
endLine: context.endLine,
|
|
320
|
+
methodCount: methods.length,
|
|
321
|
+
usageCount: callers.length,
|
|
322
|
+
methods: methods.map(m => ({
|
|
323
|
+
name: m.name,
|
|
324
|
+
file: m.file,
|
|
325
|
+
line: m.line,
|
|
326
|
+
params: m.params,
|
|
327
|
+
returnType: m.returnType,
|
|
328
|
+
receiver: m.receiver
|
|
329
|
+
})),
|
|
330
|
+
usages: callers.map(c => ({
|
|
331
|
+
file: c.relativePath || c.file,
|
|
332
|
+
line: c.line,
|
|
333
|
+
expression: c.content,
|
|
334
|
+
callerName: c.callerName
|
|
335
|
+
})),
|
|
336
|
+
...(context.warnings && { warnings: context.warnings })
|
|
337
|
+
}, null, 2);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Standard function/method context
|
|
341
|
+
const callers = context.callers || [];
|
|
342
|
+
const callees = context.callees || [];
|
|
310
343
|
return JSON.stringify({
|
|
311
344
|
function: context.function,
|
|
312
345
|
file: context.file,
|
|
313
|
-
callerCount:
|
|
314
|
-
calleeCount:
|
|
315
|
-
callers:
|
|
346
|
+
callerCount: callers.length,
|
|
347
|
+
calleeCount: callees.length,
|
|
348
|
+
callers: callers.map(c => ({
|
|
316
349
|
file: c.relativePath || c.file,
|
|
317
350
|
line: c.line,
|
|
318
351
|
expression: c.content, // FULL expression
|
|
319
352
|
callerName: c.callerName
|
|
320
353
|
})),
|
|
321
|
-
callees:
|
|
354
|
+
callees: callees.map(c => ({
|
|
322
355
|
name: c.name,
|
|
323
356
|
type: c.type,
|
|
324
357
|
file: c.relativePath || c.file,
|
|
325
358
|
line: c.startLine,
|
|
326
359
|
params: c.params, // FULL params
|
|
327
360
|
weight: c.weight || 'normal' // Dependency weight: core, setup, utility
|
|
328
|
-
}))
|
|
361
|
+
})),
|
|
362
|
+
...(context.warnings && { warnings: context.warnings })
|
|
329
363
|
}, null, 2);
|
|
330
364
|
}
|
|
331
365
|
|
package/core/project.js
CHANGED
|
@@ -166,7 +166,11 @@ class ProjectIndex {
|
|
|
166
166
|
...(item.extends && { extends: item.extends }),
|
|
167
167
|
...(item.implements && { implements: item.implements }),
|
|
168
168
|
...(item.indent !== undefined && { indent: item.indent }),
|
|
169
|
-
...(item.isNested && { isNested: item.isNested })
|
|
169
|
+
...(item.isNested && { isNested: item.isNested }),
|
|
170
|
+
...(item.isMethod && { isMethod: item.isMethod }),
|
|
171
|
+
...(item.receiver && { receiver: item.receiver }),
|
|
172
|
+
...(item.className && { className: item.className }),
|
|
173
|
+
...(item.memberType && { memberType: item.memberType })
|
|
170
174
|
};
|
|
171
175
|
fileEntry.symbols.push(symbol);
|
|
172
176
|
|
|
@@ -649,6 +653,55 @@ class ProjectIndex {
|
|
|
649
653
|
return usages;
|
|
650
654
|
}
|
|
651
655
|
|
|
656
|
+
/**
|
|
657
|
+
* Find methods that belong to a class/struct/type
|
|
658
|
+
* Works for:
|
|
659
|
+
* - Go: methods with receiver field (e.g., receiver: "*TypeName")
|
|
660
|
+
* - Python/Java: methods with className field
|
|
661
|
+
* - Rust: impl methods with receiver field
|
|
662
|
+
* @param {string} typeName - The class/struct/interface name
|
|
663
|
+
* @returns {Array} Methods belonging to this type
|
|
664
|
+
*/
|
|
665
|
+
findMethodsForType(typeName) {
|
|
666
|
+
const methods = [];
|
|
667
|
+
// Match both "TypeName" and "*TypeName" receivers (for Go/Rust pointer receivers)
|
|
668
|
+
const baseTypeName = typeName.replace(/^\*/, '');
|
|
669
|
+
|
|
670
|
+
for (const [name, symbols] of this.symbols) {
|
|
671
|
+
for (const symbol of symbols) {
|
|
672
|
+
// Skip non-method types (fields, properties, etc.)
|
|
673
|
+
if (symbol.type === 'field' || symbol.type === 'property') {
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Check Go/Rust-style receiver (e.g., func (r *Router) Method())
|
|
678
|
+
if (symbol.isMethod && symbol.receiver) {
|
|
679
|
+
const receiverBase = symbol.receiver.replace(/^\*/, '');
|
|
680
|
+
if (receiverBase === baseTypeName) {
|
|
681
|
+
methods.push(symbol);
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Check Python/Java/JS-style className (class members)
|
|
687
|
+
// Must be a method type, not just any symbol with className
|
|
688
|
+
if (symbol.className === baseTypeName &&
|
|
689
|
+
(symbol.isMethod || symbol.type === 'method' || symbol.type === 'constructor')) {
|
|
690
|
+
methods.push(symbol);
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// Sort by file then line
|
|
697
|
+
methods.sort((a, b) => {
|
|
698
|
+
if (a.relativePath !== b.relativePath) return a.relativePath.localeCompare(b.relativePath);
|
|
699
|
+
return a.startLine - b.startLine;
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
return methods;
|
|
703
|
+
}
|
|
704
|
+
|
|
652
705
|
/**
|
|
653
706
|
* Get context for a symbol (callers + callees)
|
|
654
707
|
*/
|
|
@@ -658,7 +711,53 @@ class ProjectIndex {
|
|
|
658
711
|
return { function: name, file: null, callers: [], callees: [] };
|
|
659
712
|
}
|
|
660
713
|
|
|
661
|
-
|
|
714
|
+
// Prefer class/struct/interface definitions over functions/methods/constructors
|
|
715
|
+
// This ensures context('ClassName') finds the class, not a constructor with same name
|
|
716
|
+
const typeOrder = ['class', 'struct', 'interface', 'type', 'impl'];
|
|
717
|
+
let def = definitions[0];
|
|
718
|
+
for (const d of definitions) {
|
|
719
|
+
if (typeOrder.includes(d.type)) {
|
|
720
|
+
def = d;
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// Special handling for class/struct/interface types
|
|
726
|
+
if (['class', 'struct', 'interface', 'type'].includes(def.type)) {
|
|
727
|
+
const methods = this.findMethodsForType(name);
|
|
728
|
+
|
|
729
|
+
const result = {
|
|
730
|
+
type: def.type,
|
|
731
|
+
name: name,
|
|
732
|
+
file: def.relativePath,
|
|
733
|
+
startLine: def.startLine,
|
|
734
|
+
endLine: def.endLine,
|
|
735
|
+
methods: methods.map(m => ({
|
|
736
|
+
name: m.name,
|
|
737
|
+
file: m.relativePath,
|
|
738
|
+
line: m.startLine,
|
|
739
|
+
params: m.params,
|
|
740
|
+
returnType: m.returnType,
|
|
741
|
+
receiver: m.receiver
|
|
742
|
+
})),
|
|
743
|
+
// Also include places where the type is used in function parameters/returns
|
|
744
|
+
callers: this.findCallers(name, { includeMethods: options.includeMethods })
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
if (definitions.length > 1) {
|
|
748
|
+
result.warnings = [{
|
|
749
|
+
type: 'ambiguous',
|
|
750
|
+
message: `Found ${definitions.length} definitions for "${name}". Using ${def.relativePath}:${def.startLine}. Use --file to disambiguate.`,
|
|
751
|
+
alternatives: definitions.slice(1).map(d => ({
|
|
752
|
+
file: d.relativePath,
|
|
753
|
+
line: d.startLine
|
|
754
|
+
}))
|
|
755
|
+
}];
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
return result;
|
|
759
|
+
}
|
|
760
|
+
|
|
662
761
|
const callers = this.findCallers(name, { includeMethods: options.includeMethods });
|
|
663
762
|
const callees = this.findCallees(def, { includeMethods: options.includeMethods });
|
|
664
763
|
|
|
@@ -787,8 +886,9 @@ class ProjectIndex {
|
|
|
787
886
|
if (call.isMethod) {
|
|
788
887
|
// Always skip this/self/cls calls (internal state access, not function calls)
|
|
789
888
|
if (['this', 'self', 'cls'].includes(call.receiver)) continue;
|
|
790
|
-
//
|
|
791
|
-
|
|
889
|
+
// Go doesn't use this/self/cls - always include Go method calls
|
|
890
|
+
// For other languages, skip method calls unless explicitly requested
|
|
891
|
+
if (fileEntry.language !== 'go' && !options.includeMethods) continue;
|
|
792
892
|
}
|
|
793
893
|
|
|
794
894
|
// Skip definition lines
|
|
@@ -1594,7 +1694,14 @@ class ProjectIndex {
|
|
|
1594
1694
|
for (const [name, symbols] of this.symbols) {
|
|
1595
1695
|
for (const symbol of symbols) {
|
|
1596
1696
|
// Skip non-function/class types
|
|
1597
|
-
|
|
1697
|
+
// Include various method types from different languages:
|
|
1698
|
+
// - function: standalone functions
|
|
1699
|
+
// - class, struct, interface: type definitions (skip them in deadcode)
|
|
1700
|
+
// - method: class methods
|
|
1701
|
+
// - static, public, abstract: Java method modifiers used as types
|
|
1702
|
+
// - constructor: constructors
|
|
1703
|
+
const callableTypes = ['function', 'method', 'static', 'public', 'abstract', 'constructor'];
|
|
1704
|
+
if (!callableTypes.includes(symbol.type)) {
|
|
1598
1705
|
continue;
|
|
1599
1706
|
}
|
|
1600
1707
|
|
|
@@ -1605,11 +1712,33 @@ class ProjectIndex {
|
|
|
1605
1712
|
|
|
1606
1713
|
// Check if exported
|
|
1607
1714
|
const fileEntry = this.files.get(symbol.file);
|
|
1715
|
+
const lang = fileEntry?.language;
|
|
1716
|
+
const mods = symbol.modifiers || [];
|
|
1717
|
+
|
|
1718
|
+
// Language-specific entry points (called by runtime, no AST-visible callers)
|
|
1719
|
+
// Go: main() and init() are called by runtime
|
|
1720
|
+
const isGoEntryPoint = lang === 'go' && (name === 'main' || name === 'init');
|
|
1721
|
+
|
|
1722
|
+
// Java: public static void main(String[] args) is the entry point
|
|
1723
|
+
const isJavaEntryPoint = lang === 'java' && name === 'main' &&
|
|
1724
|
+
mods.includes('public') && mods.includes('static');
|
|
1725
|
+
|
|
1726
|
+
// Python: Magic/dunder methods are called by the interpreter, not user code
|
|
1727
|
+
const isPythonMagicMethod = lang === 'python' && /^__\w+__$/.test(name);
|
|
1728
|
+
|
|
1729
|
+
// Rust: main() is entry point, #[test] functions are called by test runner
|
|
1730
|
+
const isRustEntryPoint = lang === 'rust' &&
|
|
1731
|
+
(name === 'main' || mods.includes('test'));
|
|
1732
|
+
|
|
1733
|
+
const isEntryPoint = isGoEntryPoint || isJavaEntryPoint ||
|
|
1734
|
+
isPythonMagicMethod || isRustEntryPoint;
|
|
1735
|
+
|
|
1608
1736
|
const isExported = fileEntry && (
|
|
1609
1737
|
fileEntry.exports.includes(name) ||
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
(
|
|
1738
|
+
mods.includes('export') ||
|
|
1739
|
+
mods.includes('public') ||
|
|
1740
|
+
(lang === 'go' && /^[A-Z]/.test(name)) ||
|
|
1741
|
+
isEntryPoint
|
|
1613
1742
|
);
|
|
1614
1743
|
|
|
1615
1744
|
// Skip exported unless requested
|
|
@@ -3130,7 +3259,7 @@ class ProjectIndex {
|
|
|
3130
3259
|
}
|
|
3131
3260
|
|
|
3132
3261
|
const cacheData = {
|
|
3133
|
-
version:
|
|
3262
|
+
version: 4, // v4: className, memberType, isMethod for all languages
|
|
3134
3263
|
root: this.root,
|
|
3135
3264
|
buildTime: this.buildTime,
|
|
3136
3265
|
timestamp: Date.now(),
|
|
@@ -3163,8 +3292,10 @@ class ProjectIndex {
|
|
|
3163
3292
|
try {
|
|
3164
3293
|
const cacheData = JSON.parse(fs.readFileSync(cacheFile, 'utf-8'));
|
|
3165
3294
|
|
|
3166
|
-
// Check version compatibility
|
|
3167
|
-
|
|
3295
|
+
// Check version compatibility
|
|
3296
|
+
// v4 adds className, memberType, isMethod for all languages
|
|
3297
|
+
// Only accept exactly version 4 (or future versions handled explicitly)
|
|
3298
|
+
if (cacheData.version !== 4) {
|
|
3168
3299
|
return false;
|
|
3169
3300
|
}
|
|
3170
3301
|
|
package/languages/java.js
CHANGED
|
@@ -111,6 +111,12 @@ function findFunctions(code, parser) {
|
|
|
111
111
|
if (processedRanges.has(rangeKey)) return true;
|
|
112
112
|
processedRanges.add(rangeKey);
|
|
113
113
|
|
|
114
|
+
// Skip methods inside a class body (they're extracted as class members)
|
|
115
|
+
let parent = node.parent;
|
|
116
|
+
if (parent && parent.type === 'class_body') {
|
|
117
|
+
return true; // Skip - this is a class method
|
|
118
|
+
}
|
|
119
|
+
|
|
114
120
|
const nameNode = node.childForFieldName('name');
|
|
115
121
|
const paramsNode = node.childForFieldName('parameters');
|
|
116
122
|
|
|
@@ -393,6 +399,7 @@ function extractClassMembers(classNode, code) {
|
|
|
393
399
|
endLine,
|
|
394
400
|
memberType,
|
|
395
401
|
modifiers,
|
|
402
|
+
isMethod: true, // Mark as method for context() lookups
|
|
396
403
|
...(returnType && { returnType }),
|
|
397
404
|
...(docstring && { docstring })
|
|
398
405
|
});
|
|
@@ -417,6 +424,7 @@ function extractClassMembers(classNode, code) {
|
|
|
417
424
|
endLine,
|
|
418
425
|
memberType: 'constructor',
|
|
419
426
|
modifiers,
|
|
427
|
+
isMethod: true, // Mark as method for context() lookups
|
|
420
428
|
...(docstring && { docstring })
|
|
421
429
|
});
|
|
422
430
|
}
|
package/languages/javascript.js
CHANGED
|
@@ -531,6 +531,7 @@ function extractClassMembers(classNode, code) {
|
|
|
531
531
|
memberType,
|
|
532
532
|
isAsync,
|
|
533
533
|
isGenerator: isGen,
|
|
534
|
+
isMethod: true, // Mark as method for context() lookups
|
|
534
535
|
...(returnType && { returnType }),
|
|
535
536
|
...(docstring && { docstring })
|
|
536
537
|
});
|
|
@@ -557,6 +558,7 @@ function extractClassMembers(classNode, code) {
|
|
|
557
558
|
endLine,
|
|
558
559
|
memberType: name.startsWith('#') ? 'private' : 'field',
|
|
559
560
|
isArrow: true,
|
|
561
|
+
isMethod: true, // Arrow fields are callable like methods
|
|
560
562
|
...(returnType && { returnType })
|
|
561
563
|
});
|
|
562
564
|
} else {
|
|
@@ -565,6 +567,7 @@ function extractClassMembers(classNode, code) {
|
|
|
565
567
|
startLine,
|
|
566
568
|
endLine,
|
|
567
569
|
memberType: name.startsWith('#') ? 'private field' : 'field'
|
|
570
|
+
// Not a method - regular field
|
|
568
571
|
});
|
|
569
572
|
}
|
|
570
573
|
}
|
package/languages/python.js
CHANGED
|
@@ -73,6 +73,20 @@ function findFunctions(code, parser) {
|
|
|
73
73
|
if (processedRanges.has(rangeKey)) return true;
|
|
74
74
|
processedRanges.add(rangeKey);
|
|
75
75
|
|
|
76
|
+
// Skip functions that are inside a class (they're extracted as class members)
|
|
77
|
+
let parent = node.parent;
|
|
78
|
+
// Handle decorated_definition wrapper
|
|
79
|
+
if (parent && parent.type === 'decorated_definition') {
|
|
80
|
+
parent = parent.parent;
|
|
81
|
+
}
|
|
82
|
+
// Check if parent is a class body (block inside class_definition)
|
|
83
|
+
if (parent && parent.type === 'block') {
|
|
84
|
+
const grandparent = parent.parent;
|
|
85
|
+
if (grandparent && grandparent.type === 'class_definition') {
|
|
86
|
+
return true; // Skip - this is a class method
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
76
90
|
const nameNode = node.childForFieldName('name');
|
|
77
91
|
const paramsNode = node.childForFieldName('parameters');
|
|
78
92
|
|
|
@@ -282,6 +296,7 @@ function extractClassMembers(classNode, code) {
|
|
|
282
296
|
endLine,
|
|
283
297
|
memberType,
|
|
284
298
|
isAsync,
|
|
299
|
+
isMethod: true, // Mark as method for context() lookups
|
|
285
300
|
...(returnType && { returnType }),
|
|
286
301
|
...(docstring && { docstring }),
|
|
287
302
|
...(memberDecorators.length > 0 && { decorators: memberDecorators })
|