ucn 3.7.40 → 3.7.41

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 CHANGED
@@ -153,7 +153,7 @@ if (unknownFlags.length > 0) {
153
153
  const VALUE_FLAGS = new Set([
154
154
  '--file', '--depth', '--top', '--context', '--direction',
155
155
  '--add-param', '--remove-param', '--rename-to', '--default',
156
- '--base', '--exclude', '--not', '--in', '--max-lines'
156
+ '--base', '--exclude', '--not', '--in', '--max-lines', '--class-name'
157
157
  ]);
158
158
 
159
159
  // Remove flags from args, then add args after -- (which are all positional)
@@ -555,7 +555,7 @@ function runProjectCommand(rootDir, command, arg) {
555
555
  }
556
556
 
557
557
  case 'verify': {
558
- const { ok, result, error } = execute(index, 'verify', { name: arg, file: flags.file });
558
+ const { ok, result, error } = execute(index, 'verify', { name: arg, ...flags });
559
559
  if (!ok) fail(error);
560
560
  printOutput(result, output.formatVerifyJson, output.formatVerify);
561
561
  break;
package/core/callers.js CHANGED
@@ -524,11 +524,18 @@ function findCallees(index, def, options = {}) {
524
524
  if (bindings.length === 0 && call.isMethod) {
525
525
  if (language !== 'go' && language !== 'java' && language !== 'rust') {
526
526
  // JS/TS/Python: mark uncertain unless receiver has import/binding
527
- // evidence in file scope. Prevents false positives like m.get() →
528
- // repository.get() when m is just a parameter with no type info.
529
- const hasReceiverEvidence = call.receiver &&
530
- fileEntry?.bindings?.some(b => b.name === call.receiver);
531
- if (!hasReceiverEvidence) {
527
+ // evidence in file scope AND that binding can plausibly have this method.
528
+ // Prevents false positives like m.get() → repository.get() when m is
529
+ // just a parameter, AND dict.get() → api.get() when dict is a state object.
530
+ const receiverBinding = call.receiver &&
531
+ fileEntry?.bindings?.find(b => b.name === call.receiver);
532
+ if (!receiverBinding) {
533
+ isUncertain = true;
534
+ } else if (receiverBinding.type === 'state') {
535
+ // State objects (module-level dicts/lists) don't have user-defined methods
536
+ isUncertain = true;
537
+ } else if (receiverBinding.type === 'function') {
538
+ // Functions don't have user-defined methods (return value is unknown)
532
539
  isUncertain = true;
533
540
  }
534
541
  } else {
@@ -542,6 +549,22 @@ function findCallees(index, def, options = {}) {
542
549
  }
543
550
  }
544
551
  if (bindings.length === 1) {
552
+ // For method calls with a receiver, verify the receiver plausibly
553
+ // matches the binding's class. Prevents plt.close() → ReportGenerator.close()
554
+ // when close is defined in the same file as a class method.
555
+ if (call.isMethod && call.receiver && bindings[0].type === 'method' &&
556
+ language !== 'go' && language !== 'java' && language !== 'rust') {
557
+ // The binding is a class method — check if the receiver could be an instance
558
+ const bindingSym = index.symbols.get(call.name)?.find(
559
+ s => s.bindingId === bindings[0].id);
560
+ if (bindingSym?.className) {
561
+ // Receiver is not a known instance of this class → uncertain
562
+ const receiverType = localTypes?.get(call.receiver);
563
+ if (receiverType !== bindingSym.className) {
564
+ isUncertain = true;
565
+ }
566
+ }
567
+ }
545
568
  bindingResolved = bindings[0].id;
546
569
  calleeKey = bindingResolved;
547
570
  } else if (bindings.length > 1) {
package/core/project.js CHANGED
@@ -1032,7 +1032,8 @@ class ProjectIndex {
1032
1032
  line: u.line,
1033
1033
  content: lineContent,
1034
1034
  usageType: u.usageType,
1035
- isDefinition: false
1035
+ isDefinition: false,
1036
+ ...(u.receiver && { receiver: u.receiver })
1036
1037
  };
1037
1038
 
1038
1039
  // Add context lines if requested
package/languages/go.js CHANGED
@@ -804,6 +804,12 @@ function findUsagesInCode(code, name, parser) {
804
804
  } else {
805
805
  usageType = 'reference';
806
806
  }
807
+ // Track receiver for selector expressions (obj.name → receiver = 'obj')
808
+ const operand = parent.childForFieldName('operand');
809
+ if (operand && operand.type === 'identifier') {
810
+ usages.push({ line, column, usageType, receiver: operand.text });
811
+ return true;
812
+ }
807
813
  }
808
814
  }
809
815
 
package/languages/java.js CHANGED
@@ -887,6 +887,12 @@ function findUsagesInCode(code, name, parser) {
887
887
  else if (parent.type === 'method_invocation' &&
888
888
  parent.childForFieldName('name') === node) {
889
889
  usageType = 'call';
890
+ // Track receiver for method invocations (obj.name() → receiver = 'obj')
891
+ const object = parent.childForFieldName('object');
892
+ if (object && object.type === 'identifier') {
893
+ usages.push({ line, column, usageType, receiver: object.text });
894
+ return true;
895
+ }
890
896
  }
891
897
  // Definition: method name
892
898
  else if (parent.type === 'method_declaration' &&
@@ -946,6 +952,12 @@ function findUsagesInCode(code, name, parser) {
946
952
  else if (parent.type === 'field_access' &&
947
953
  parent.childForFieldName('field') === node) {
948
954
  usageType = 'reference';
955
+ // Track receiver for field access (obj.field → receiver = 'obj')
956
+ const object = parent.childForFieldName('object');
957
+ if (object && object.type === 'identifier') {
958
+ usages.push({ line, column, usageType, receiver: object.text });
959
+ return true;
960
+ }
949
961
  }
950
962
  }
951
963
 
@@ -1824,6 +1824,11 @@ function findUsagesInCode(code, name, parser) {
1824
1824
  } else {
1825
1825
  usageType = 'reference';
1826
1826
  }
1827
+ // Track receiver for member expressions (obj.name → receiver = 'obj')
1828
+ if (object && object.type === 'identifier') {
1829
+ usages.push({ line, column, usageType, receiver: object.text });
1830
+ return true;
1831
+ }
1827
1832
  }
1828
1833
  // JSX component usage: <Component /> or <Component>...</Component>
1829
1834
  else if (parent.type === 'jsx_self_closing_element' || parent.type === 'jsx_opening_element') {
@@ -887,6 +887,12 @@ function findUsagesInCode(code, name, parser) {
887
887
  } else {
888
888
  usageType = 'reference';
889
889
  }
890
+ // Track receiver for member expressions (obj.name → receiver = 'obj')
891
+ const object = parent.childForFieldName('object');
892
+ if (object && object.type === 'identifier') {
893
+ usages.push({ line, column, usageType, receiver: object.text });
894
+ return true;
895
+ }
890
896
  }
891
897
  }
892
898
 
package/languages/rust.js CHANGED
@@ -1135,6 +1135,12 @@ function findUsagesInCode(code, name, parser) {
1135
1135
  } else {
1136
1136
  usageType = 'reference';
1137
1137
  }
1138
+ // Track receiver for field expressions (obj.name → receiver = 'obj')
1139
+ const value = parent.childForFieldName('value');
1140
+ if (value && value.type === 'identifier') {
1141
+ usages.push({ line, column, usageType, receiver: value.text });
1142
+ return true;
1143
+ }
1138
1144
  }
1139
1145
  }
1140
1146
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ucn",
3
- "version": "3.7.40",
3
+ "version": "3.7.41",
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",