ucn 3.7.39 → 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
@@ -101,6 +101,7 @@ function parseFlags(tokens) {
101
101
  maxLines: getValueFlag('--max-lines') || null,
102
102
  regex: tokens.includes('--no-regex') ? false : undefined,
103
103
  functions: tokens.includes('--functions'),
104
+ className: getValueFlag('--class-name'),
104
105
  };
105
106
  }
106
107
 
@@ -125,7 +126,7 @@ const knownFlags = new Set([
125
126
  '--default', '--top', '--no-follow-symlinks',
126
127
  '--base', '--staged', '--stack',
127
128
  '--regex', '--no-regex', '--functions',
128
- '--max-lines'
129
+ '--max-lines', '--class-name'
129
130
  ]);
130
131
 
131
132
  // Handle help flag
@@ -152,7 +153,7 @@ if (unknownFlags.length > 0) {
152
153
  const VALUE_FLAGS = new Set([
153
154
  '--file', '--depth', '--top', '--context', '--direction',
154
155
  '--add-param', '--remove-param', '--rename-to', '--default',
155
- '--base', '--exclude', '--not', '--in', '--max-lines'
156
+ '--base', '--exclude', '--not', '--in', '--max-lines', '--class-name'
156
157
  ]);
157
158
 
158
159
  // Remove flags from args, then add args after -- (which are all positional)
@@ -554,7 +555,7 @@ function runProjectCommand(rootDir, command, arg) {
554
555
  }
555
556
 
556
557
  case 'verify': {
557
- const { ok, result, error } = execute(index, 'verify', { name: arg, file: flags.file });
558
+ const { ok, result, error } = execute(index, 'verify', { name: arg, ...flags });
558
559
  if (!ok) fail(error);
559
560
  printOutput(result, output.formatVerifyJson, output.formatVerify);
560
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
@@ -2745,9 +2746,9 @@ class ProjectIndex {
2745
2746
  }
2746
2747
  }
2747
2748
  }
2748
- // When className is explicitly set and we can't determine the receiver type,
2749
- // still include the call (conservative: prefer false positives over false negatives)
2750
- return true;
2749
+ // className explicitly set but receiver type unknown filter it out.
2750
+ // User asked for a specific class; unknown receivers are likely unrelated.
2751
+ return false;
2751
2752
  });
2752
2753
  }
2753
2754
 
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.39",
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",