ucn 3.7.32 → 3.7.34

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/core/callers.js CHANGED
@@ -227,47 +227,54 @@ function findCallers(index, name, options = {}) {
227
227
  if (call.selfAttribute && fileEntry.language === 'python') {
228
228
  // self.attr.method() — resolve via attribute type inference
229
229
  const callerSymbol = index.findEnclosingFunction(filePath, call.line, true);
230
- if (!callerSymbol?.className) continue;
231
- const attrTypes = getInstanceAttributeTypes(index, filePath, callerSymbol.className);
232
- if (!attrTypes) continue;
233
- const targetClass = attrTypes.get(call.selfAttribute);
234
- if (!targetClass) continue;
235
- // Check if any definition of searched function belongs to targetClass
236
- const matchesDef = definitions.some(d => d.className === targetClass);
237
- if (!matchesDef) continue;
238
- resolvedBySameClass = true;
239
- // Falls through to add as caller
230
+ if (!callerSymbol?.className) {
231
+ // Can't resolve include only if includeMethods requested
232
+ if (!options.includeMethods) continue;
233
+ } else {
234
+ const attrTypes = getInstanceAttributeTypes(index, filePath, callerSymbol.className);
235
+ const targetClass = attrTypes?.get(call.selfAttribute);
236
+ if (targetClass && definitions.some(d => d.className === targetClass)) {
237
+ resolvedBySameClass = true;
238
+ } else if (!options.includeMethods) {
239
+ continue;
240
+ }
241
+ }
240
242
  } else if (['self', 'cls', 'this', 'super'].includes(call.receiver)) {
241
243
  // self/this/super.method() — resolve to same-class or parent method
242
244
  const callerSymbol = index.findEnclosingFunction(filePath, call.line, true);
243
- if (!callerSymbol?.className) continue;
244
- // For super(), skip same-class — only check parent chain
245
- let matchesDef = call.receiver === 'super'
246
- ? false
247
- : definitions.some(d => d.className === callerSymbol.className);
248
- // Walk inheritance chain using BFS if not found in same class
249
- if (!matchesDef) {
250
- const visited = new Set([callerSymbol.className]);
251
- const callerFile = callerSymbol.file || filePath;
252
- const startParents = index._getInheritanceParents(callerSymbol.className, callerFile) || [];
253
- const queue = startParents.map(p => ({ name: p, contextFile: callerFile }));
254
- while (queue.length > 0 && !matchesDef) {
255
- const { name: current, contextFile } = queue.shift();
256
- if (visited.has(current)) continue;
257
- visited.add(current);
258
- matchesDef = definitions.some(d => d.className === current);
259
- if (!matchesDef) {
260
- const resolvedFile = index._resolveClassFile(current, contextFile);
261
- const grandparents = index._getInheritanceParents(current, resolvedFile) || [];
262
- for (const gp of grandparents) {
263
- if (!visited.has(gp)) queue.push({ name: gp, contextFile: resolvedFile });
245
+ if (!callerSymbol?.className) {
246
+ if (!options.includeMethods) continue;
247
+ } else {
248
+ // For super(), skip same-class — only check parent chain
249
+ let matchesDef = call.receiver === 'super'
250
+ ? false
251
+ : definitions.some(d => d.className === callerSymbol.className);
252
+ // Walk inheritance chain using BFS if not found in same class
253
+ if (!matchesDef) {
254
+ const visited = new Set([callerSymbol.className]);
255
+ const callerFile = callerSymbol.file || filePath;
256
+ const startParents = index._getInheritanceParents(callerSymbol.className, callerFile) || [];
257
+ const queue = startParents.map(p => ({ name: p, contextFile: callerFile }));
258
+ while (queue.length > 0 && !matchesDef) {
259
+ const { name: current, contextFile } = queue.shift();
260
+ if (visited.has(current)) continue;
261
+ visited.add(current);
262
+ matchesDef = definitions.some(d => d.className === current);
263
+ if (!matchesDef) {
264
+ const resolvedFile = index._resolveClassFile(current, contextFile);
265
+ const grandparents = index._getInheritanceParents(current, resolvedFile) || [];
266
+ for (const gp of grandparents) {
267
+ if (!visited.has(gp)) queue.push({ name: gp, contextFile: resolvedFile });
268
+ }
264
269
  }
265
270
  }
266
271
  }
272
+ if (matchesDef) {
273
+ resolvedBySameClass = true;
274
+ } else if (!options.includeMethods) {
275
+ continue;
276
+ }
267
277
  }
268
- if (!matchesDef) continue;
269
- resolvedBySameClass = true;
270
- // Falls through to add as caller
271
278
  } else {
272
279
  // Go doesn't use this/self/cls - always include Go method calls
273
280
  // Java method calls are always obj.method() - include by default
@@ -982,6 +982,30 @@ function findInstanceAttributeTypes(code, parser) {
982
982
  const initBody = child.childForFieldName('body');
983
983
  if (!initBody) continue;
984
984
 
985
+ // Build parameter type map from __init__ annotations
986
+ // e.g. def __init__(self, market: MarketDataFetcher = None) → {market: MarketDataFetcher}
987
+ const paramTypes = new Map();
988
+ const params = child.childForFieldName('parameters');
989
+ if (params) {
990
+ for (let p = 0; p < params.childCount; p++) {
991
+ const param = params.child(p);
992
+ // typed_parameter or typed_default_parameter
993
+ if (param.type === 'typed_parameter' || param.type === 'typed_default_parameter') {
994
+ const pName = param.childForFieldName('name') || param.child(0);
995
+ const pType = param.childForFieldName('type');
996
+ if (pName && pType) {
997
+ const typeIdent = pType.type === 'type' ? pType.firstChild : pType;
998
+ if (typeIdent?.type === 'identifier') {
999
+ const tn = typeIdent.text;
1000
+ if (!PRIMITIVE_TYPES.has(tn) && tn[0] >= 'A' && tn[0] <= 'Z') {
1001
+ paramTypes.set(pName.text, tn);
1002
+ }
1003
+ }
1004
+ }
1005
+ }
1006
+ }
1007
+ }
1008
+
985
1009
  traverseTree(initBody, (stmt) => {
986
1010
  if (stmt.type !== 'expression_statement') return true;
987
1011
 
@@ -1004,6 +1028,9 @@ function findInstanceAttributeTypes(code, parser) {
1004
1028
  const typeName = extractConstructorName(rhs);
1005
1029
  if (typeName) {
1006
1030
  attrTypes.set(attrName, typeName);
1031
+ } else if (rhs.type === 'identifier' && paramTypes.has(rhs.text)) {
1032
+ // self.X = param where param has type annotation
1033
+ attrTypes.set(attrName, paramTypes.get(rhs.text));
1007
1034
  }
1008
1035
 
1009
1036
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ucn",
3
- "version": "3.7.32",
3
+ "version": "3.7.34",
4
4
  "mcpName": "io.github.mleoca/ucn",
5
5
  "description": "Universal Code Navigator — AST-based call graph analysis for AI agents. Find callers, trace impact, detect dead code across JS/TS, Python, Go, Rust, Java, and HTML. CLI, MCP server, and agent skill.",
6
6
  "main": "index.js",