ucn 3.7.40 → 3.7.42

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
@@ -1018,12 +1018,38 @@ class ProjectIndex {
1018
1018
  // Try AST-based detection first (with per-operation cache)
1019
1019
  const astUsages = this._getCachedUsages(filePath, name);
1020
1020
  if (astUsages !== null) {
1021
+ // Pre-compute: does any imported project file define this name?
1022
+ // Used to filter namespace member expressions (e.g., DropdownMenuPrimitive.Separator)
1023
+ // while keeping module access patterns (e.g., output.formatExample())
1024
+ let _importedHasDef = null;
1025
+ const importedFileHasDef = () => {
1026
+ if (_importedHasDef !== null) return _importedHasDef;
1027
+ const importedFiles = this.importGraph.get(filePath) || [];
1028
+ _importedHasDef = importedFiles.some(imp => {
1029
+ const impEntry = this.files.get(imp);
1030
+ return impEntry?.symbols?.some(s => s.name === name);
1031
+ });
1032
+ return _importedHasDef;
1033
+ };
1034
+
1021
1035
  for (const u of astUsages) {
1022
1036
  // Skip if this is a definition line (already added above)
1023
1037
  if (definitions.some(d => d.file === filePath && d.startLine === u.line)) {
1024
1038
  continue;
1025
1039
  }
1026
1040
 
1041
+ // Filter member expressions with unrelated receivers in JS/TS/Python.
1042
+ // Keeps: standalone usages, self/this/cls/super, method calls on known types,
1043
+ // and module access (output.fn()) when the imported file defines the name.
1044
+ // Filters: namespace access to external packages (DropdownMenuPrimitive.Separator).
1045
+ if (u.receiver && !['self', 'this', 'cls', 'super'].includes(u.receiver) &&
1046
+ fileEntry.language !== 'go' && fileEntry.language !== 'java' && fileEntry.language !== 'rust') {
1047
+ const hasMethodDef = allDefinitions.some(d => d.className);
1048
+ if (!hasMethodDef && !importedFileHasDef()) {
1049
+ continue;
1050
+ }
1051
+ }
1052
+
1027
1053
  const lineContent = lines[u.line - 1] || '';
1028
1054
 
1029
1055
  const usage = {
@@ -1032,7 +1058,8 @@ class ProjectIndex {
1032
1058
  line: u.line,
1033
1059
  content: lineContent,
1034
1060
  usageType: u.usageType,
1035
- isDefinition: false
1061
+ isDefinition: false,
1062
+ ...(u.receiver && { receiver: u.receiver })
1036
1063
  };
1037
1064
 
1038
1065
  // 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.42",
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",