ucn 5.3.2 → 5.3.3

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.
@@ -161,7 +161,15 @@ Treat `deadcode` as a candidate generator. Before deletion, inspect `usages`, `i
161
161
  (JSX children, HTML markup and attributes) in an `OTHER TEXT` section unless
162
162
  `--code-only` is set, so it lists every line the `ACCOUNT` counts. This is a
163
163
  literal-name inventory, not exact target binding. Identifier boundaries are
164
- Unicode-aware: `hit` never matches inside `hitΔ`.
164
+ Unicode-aware and match `grep -w`: `hit` never matches inside `hitΔ`, while
165
+ `$` is a boundary (`buy${...}`, `$fail`, `ws$close()` all count).
166
+
167
+ Aliased and qualified calls resolve in every language: Rust `use m::f as g; g()`
168
+ is a caller of `f` (listed as a beyond-text caller, since the line holds no
169
+ target token); Java `pkg.Type.method()` and C# `Ns.Type.Method()` /
170
+ `using T = Ns.Type; T.Method()` pick the type the qualifier names when several
171
+ same-name types exist. A qualifier the resolver cannot place stays visible as
172
+ `method-ambiguous`, never confirmed by first-definition order.
165
173
 
166
174
  `endpoints` recognizes client receivers by evidence (a receiver typed to an
167
175
  HTTP client class, or bound to a pytest fixture that constructs one), not only
package/core/cache.js CHANGED
@@ -699,7 +699,7 @@ function clearAllCaches() {
699
699
  // v212 (fix #342): extendsGraph/extendedByGraph no longer persisted (rebuilt on load).
700
700
  // v213 (fix #348): Python from-import records carry per-name `renames` so
701
701
  // import bindings pair each alias with its own module.
702
- const CACHE_FORMAT_VERSION = 213;
702
+ const CACHE_FORMAT_VERSION = 214;
703
703
  const USAGE_CACHE_FILE = 'usage-results.json';
704
704
 
705
705
  /**
package/core/callers.js CHANGED
@@ -10,7 +10,8 @@ const path = require('path');
10
10
  const crypto = require('crypto');
11
11
  const { detectLanguage, getParser, getLanguageAdapter, langTraits } = require('../languages');
12
12
  const { isTestFile } = require('./discovery');
13
- const { NON_CALLABLE_TYPES, isOverrideMarked, codeUnitCompare, isTestPath } = require('./shared');
13
+ const { NON_CALLABLE_TYPES, isOverrideMarked, codeUnitCompare, isTestPath, CALLABLE_SYMBOL_KINDS } = require('./shared');
14
+ const { _resolveJavaPackageImport } = require('./graph-build');
14
15
  const { scoreEdge, tierForResolution, TIER } = require('./confidence');
15
16
  const { findGoModule, resolveRustImport } = require('./imports');
16
17
 
@@ -634,6 +635,17 @@ function findCallers(index, name, options = {}) {
634
635
  langTraits(fileEntry.language)?.typeSystem === 'structural';
635
636
 
636
637
  for (let call of calls) {
638
+ // fix #353: C# `Beta.Helper.Widget()` — the parser records a
639
+ // field hop rooted at `this` (Beta is no local). When the
640
+ // prefix names a project NAMESPACE that declares the last
641
+ // segment as a type, the receiver is that type, namespace-
642
+ // qualified; the hop shape would only ever fail the field
643
+ // walk and route method-ambiguous.
644
+ if (fileEntry.language === 'csharp') {
645
+ const rewritten = _csharpNamespaceQualifiedReceiver(index, call,
646
+ index.findEnclosingFunction(filePath, call.line, true));
647
+ if (rewritten) call = rewritten;
648
+ }
637
649
  // Skip if not matching our target name (also check alias resolution)
638
650
  let calledAs = null; // surface name when matched via an import/export rename
639
651
  const typeQualifierReference = targetIsTypeQuery &&
@@ -2808,6 +2820,16 @@ function findCallers(index, name, options = {}) {
2808
2820
  const paired = nameBindings.filter(b => b.alias === call.name);
2809
2821
  if (paired.length > 0) nameBindings = paired;
2810
2822
  }
2823
+ // Scope-granular import bindings (fix #352): a
2824
+ // function-local `from x import name` binds the name for
2825
+ // ITS function only. Three such imports in one file used
2826
+ // to make three file-level bindings, so a bare call in any
2827
+ // of the functions scope-matched every pin (investment
2828
+ // run_validation: 6 defs, each claiming the others'
2829
+ // sites). The nearest enclosing binder owns the name; a
2830
+ // binding inside a function that does not enclose the
2831
+ // call is out of scope (#215 discipline, line-granular).
2832
+ nameBindings = _scopeImportBindings(fileEntry, nameBindings, call.line);
2811
2833
  const tFiles = new Set(targetDefs.map(d => d.file).filter(Boolean));
2812
2834
  // fix #215 (rich-measured: 225 builtin `print(...)` calls
2813
2835
  // confirmed against rich's def via file-level import edges):
@@ -4075,12 +4097,17 @@ function findCallers(index, name, options = {}) {
4075
4097
  if (call.isPathCall && receiverName) {
4076
4098
  receiverName = String(receiverName).split('::').pop();
4077
4099
  }
4100
+ let aliasResolvedFile = null;
4078
4101
  if (receiverName && !tTypes.has(receiverName)) {
4079
4102
  for (const im of (fileEntry.importBindings || [])) {
4080
4103
  if (im.name !== receiverName) continue;
4081
- const orig = String(im.module || '').split('::').pop();
4104
+ // fix #353: C# `using BH = Beta.Helper` (and Java
4105
+ // dotted paths) split on `.`; Rust paths on `::`.
4106
+ const orig = String(im.module || '').split(/::|\./).pop();
4082
4107
  if (orig && orig !== receiverName && tTypes.has(orig)) {
4083
4108
  receiverName = orig;
4109
+ const rel = fileEntry.moduleResolved && fileEntry.moduleResolved[im.module];
4110
+ if (rel) aliasResolvedFile = path.join(index.root, rel);
4084
4111
  break;
4085
4112
  }
4086
4113
  }
@@ -4108,7 +4135,14 @@ function findCallers(index, name, options = {}) {
4108
4135
  // name fallback above handles multi-definition names; this
4109
4136
  // covers single-definition targets that skip it.
4110
4137
  if (typeQualifiedReceiver) {
4111
- const identity = _resolveReceiverTypeIdentity(index, filePath, receiverName, targetDefs2, call.line);
4138
+ // fix #353: an alias binding that RESOLVED to a file is
4139
+ // the type's identity (`using BH = Beta.Helper`); a
4140
+ // parser-recorded qualifier (`beta.Helper`, `Beta.Helper`)
4141
+ // is a namespace/package hint for the resolver.
4142
+ const identity = aliasResolvedFile
4143
+ ? (targetDefs2.some(d => d.file === aliasResolvedFile) ? 'target' : 'other')
4144
+ : _resolveReceiverTypeIdentity(index, filePath, receiverName, targetDefs2, call.line,
4145
+ call.receiverIsTypeQualified ? call.receiverTypeQualifier : undefined);
4112
4146
  if (identity === 'other') {
4113
4147
  recordExcluded(filePath, call.line, 'path-type-mismatch');
4114
4148
  continue;
@@ -5344,6 +5378,12 @@ function findCallees(index, definition, options = {}) {
5344
5378
  for (let call of calls) {
5345
5379
  siteOrdinal++;
5346
5380
  const siteId = siteOrdinal;
5381
+ if (language === 'csharp') {
5382
+ // fix #353: `Beta.Helper.Widget()` — namespace-qualified type
5383
+ // receiver (see the findCallers twin).
5384
+ const rewritten = _csharpNamespaceQualifiedReceiver(index, call, def);
5385
+ if (rewritten) call = rewritten;
5386
+ }
5347
5387
  if (language === 'go' && call.isMethod &&
5348
5388
  !call.receiverType && call.receiverIndexField) {
5349
5389
  const indexedType = _goIndexedReceiverType(index, def.file, call);
@@ -9853,6 +9893,42 @@ function _projectTopLevelNames(index) {
9853
9893
  return names;
9854
9894
  }
9855
9895
 
9896
+ /**
9897
+ * Fix #352: restrict import bindings of a name to those in scope at a call
9898
+ * line. A binding whose import line sits inside a function body is local to
9899
+ * that function (Python function-body imports, JS function-scoped require);
9900
+ * the innermost enclosing binder wins, bindings in non-enclosing functions
9901
+ * are dropped, module-level bindings survive only when no enclosing function
9902
+ * binds the name. Bindings without a line (older records) are kept as-is.
9903
+ */
9904
+ function _scopeImportBindings(fileEntry, bindings, callLine) {
9905
+ if (!bindings || bindings.length < 2 || callLine == null) return bindings;
9906
+ if (!bindings.some(b => b.line != null && b.deferred)) return bindings;
9907
+ const scopes = (fileEntry.symbols || []).filter(s =>
9908
+ s.startLine != null && s.endLine != null && s.endLine > s.startLine &&
9909
+ CALLABLE_SYMBOL_KINDS.has(s.type));
9910
+ const innermost = line => {
9911
+ let best = null;
9912
+ for (const s of scopes) {
9913
+ if (line < s.startLine || line > s.endLine) continue;
9914
+ if (!best || (s.endLine - s.startLine) < (best.endLine - best.startLine)) best = s;
9915
+ }
9916
+ return best;
9917
+ };
9918
+ const local = [];
9919
+ const moduleLevel = [];
9920
+ for (const b of bindings) {
9921
+ if (b.line == null) { moduleLevel.push(b); continue; }
9922
+ const scope = innermost(b.line);
9923
+ if (!scope) { moduleLevel.push(b); continue; }
9924
+ if (callLine < scope.startLine || callLine > scope.endLine) continue;
9925
+ local.push({ b, size: scope.endLine - scope.startLine });
9926
+ }
9927
+ if (local.length === 0) return moduleLevel;
9928
+ const nearest = Math.min(...local.map(l => l.size));
9929
+ return local.filter(l => l.size === nearest).map(l => l.b);
9930
+ }
9931
+
9856
9932
  /**
9857
9933
  * Is an UNRESOLVED module specifier a resolver gap rather than externality
9858
9934
  * evidence? (fix #337b) Relative specifiers and first segments naming a
@@ -9980,6 +10056,32 @@ function _isGenericParamReceiverType(index, filePath, line, typeName) {
9980
10056
  return _isEnclosingGenericParam(index, filePath, line, typeName);
9981
10057
  }
9982
10058
 
10059
+ /**
10060
+ * fix #353: C# namespace-qualified type receivers. `Beta.Helper.Widget()` is
10061
+ * recorded by the parser as a this-rooted field hop (Beta is not a local);
10062
+ * when the dotted prefix names a project namespace (exactly, or relative to
10063
+ * the call's own namespace) that declares the last segment as a type, the
10064
+ * call is a type-qualified static call on that type. Returns a rewritten
10065
+ * record or null (unknown prefixes keep the parser's shape).
10066
+ */
10067
+ function _csharpNamespaceQualifiedReceiver(index, call, enclosing) {
10068
+ if (!call.isMethod || call.receiverType || !Array.isArray(call.receiverFields) ||
10069
+ call.receiverFields.length < 2 || call.receiverRoot !== 'this') return null;
10070
+ const typeName = call.receiverFields[call.receiverFields.length - 1];
10071
+ if (!/^[A-Z]/.test(typeName)) return null;
10072
+ const prefix = call.receiverFields.slice(0, -1).join('.');
10073
+ const typeDefs = (index.symbols.get(typeName) || []).filter(d =>
10074
+ IDENTITY_TYPE_KINDS.has(d.type) && d.namespace);
10075
+ if (typeDefs.length === 0) return null;
10076
+ const enclosingNs = enclosing?.namespace || null;
10077
+ const candidates = enclosingNs ? [prefix, `${enclosingNs}.${prefix}`] : [prefix];
10078
+ const ns = candidates.find(c => typeDefs.some(d => d.namespace === c));
10079
+ if (!ns) return null;
10080
+ const { receiverRoot, receiverField, receiverFields, receiverRootType, receiverRootNamespace, ...rest } = call;
10081
+ void receiverRoot; void receiverField; void receiverFields; void receiverRootType; void receiverRootNamespace;
10082
+ return { ...rest, receiver: typeName, receiverIsTypeQualified: true, receiverTypeQualifier: ns };
10083
+ }
10084
+
9983
10085
  /**
9984
10086
  * Java same-package check across Maven/Gradle source roots (fix #246):
9985
10087
  * src/main/java/<pkg> and src/test/java/<pkg> hold the SAME package —
@@ -10019,6 +10121,16 @@ function _resolveReceiverTypeIdentity(index, filePath, knownType, targetDefs, li
10019
10121
  // cannot resolve it. That is not exclusion evidence.
10020
10122
  return 'unknown';
10021
10123
  }
10124
+ if (language === 'java' && namespaceHint && /^[a-z_]/.test(namespaceHint)) {
10125
+ // fix #353: a lowercase dotted qualifier is a PACKAGE
10126
+ // (`beta.Helper.widget()`); the package + type name resolve to one
10127
+ // file exactly like an import of `beta.Helper` would. Unresolvable
10128
+ // packages (external, resolver gap) are never exclusion evidence.
10129
+ const resolved = _resolveJavaPackageImport(index, `${namespaceHint}.${knownType}`, null);
10130
+ if (!resolved) return 'unknown';
10131
+ return targetDefs.some(d => d.className === knownType && d.file === resolved)
10132
+ ? 'target' : 'other';
10133
+ }
10022
10134
  if (language === 'java' && line == null) {
10023
10135
  // Return-flow annotations are interpreted in the PRODUCER definition's
10024
10136
  // file. That origin is stronger than same-package lookup and is
@@ -10965,6 +11077,23 @@ function _calleeStructuralBindingRoute(index, fileEntry, call, language, binding
10965
11077
  let sawProjectish = false;
10966
11078
  let sawUnknown = false;
10967
11079
  for (const binding of bindings) {
11080
+ // fix #353 (Rust): `use alpha::widget as renamed; renamed()` — the
11081
+ // binding's module is the ITEM path; the item is its last segment
11082
+ // and the owning module file resolves through the Rust resolver.
11083
+ if (language === 'rust') {
11084
+ const segs = String(binding.module || '').split('::').filter(Boolean);
11085
+ const item = segs[segs.length - 1];
11086
+ const owners = _rustBindingResolvedFiles(index, fileEntry, fileEntry.path, binding);
11087
+ if (item && owners.size > 0) {
11088
+ sawProjectish = true;
11089
+ for (const moduleFile of owners) {
11090
+ const routed = _calleeExportDefinitions(index, moduleFile, item, language, call, {});
11091
+ for (const d of routed.matches) matches.set(`${d.file}:${d.startLine}`, d);
11092
+ if (routed.unknown) sawUnknown = true;
11093
+ }
11094
+ continue;
11095
+ }
11096
+ }
10968
11097
  const rel = fileEntry.moduleResolved && fileEntry.moduleResolved[binding.module];
10969
11098
  if (!rel) {
10970
11099
  if (_unresolvedModuleIsGap(index, binding.module, binding)) {
@@ -11722,6 +11851,42 @@ function _nonCallableFieldMember(index, typeName, name, language) {
11722
11851
  * (#215): the class defined in this file or a file binding of the name —
11723
11852
  * an unbound capitalized receiver may be a parameter or local.
11724
11853
  */
11854
+ /**
11855
+ * fix #353: resolve the files that OWN a type-qualified static receiver from
11856
+ * the qualifier in the call (Java package / C# namespace), the file's alias
11857
+ * or name import binding of the receiver, or the caller's own namespace.
11858
+ * Returns a Set of absolute files, or null when nothing pins the owner.
11859
+ */
11860
+ function _qualifiedStaticOwnerFiles(index, fileEntry, call, typeName, language, def) {
11861
+ const files = new Set();
11862
+ const typeDefs = (index.symbols.get(typeName) || []).filter(d =>
11863
+ IDENTITY_TYPE_KINDS.has(d.type) && d.file);
11864
+ const qual = call.receiverIsTypeQualified ? call.receiverTypeQualifier : null;
11865
+ if (qual) {
11866
+ if (language === 'java' && /^[a-z_]/.test(qual)) {
11867
+ const resolved = _resolveJavaPackageImport(index, `${qual}.${typeName}`, null);
11868
+ if (resolved) files.add(resolved);
11869
+ return files.size > 0 ? files : null;
11870
+ }
11871
+ if (language === 'csharp') {
11872
+ const enclosingNs = def?.namespace || null;
11873
+ const candidates = enclosingNs ? [qual, `${enclosingNs}.${qual}`] : [qual];
11874
+ for (const d of typeDefs) {
11875
+ if (candidates.includes(d.namespace)) files.add(d.file);
11876
+ }
11877
+ return files.size > 0 ? files : null;
11878
+ }
11879
+ }
11880
+ for (const b of (fileEntry?.importBindings || [])) {
11881
+ const bindsReceiver = b.name === call.receiver || b.alias === call.receiver ||
11882
+ (b.name === typeName && !b.alias);
11883
+ if (!bindsReceiver) continue;
11884
+ const rel = fileEntry.moduleResolved && fileEntry.moduleResolved[b.module];
11885
+ if (rel) files.add(path.join(index.root, rel));
11886
+ }
11887
+ return files.size > 0 ? files : null;
11888
+ }
11889
+
11725
11890
  /**
11726
11891
  * Namespace/module-container resolution (fix #254, W8 BUG-4 — verify's
11727
11892
  * BUG-BX rule brought into the engine, range-based): `Utils.slug()` where a
@@ -11835,7 +12000,8 @@ function _calleeTypeQualifiedReceiver(index, def, fileEntry, call, language) {
11835
12000
  if (typeDefs.length === 0) {
11836
12001
  for (const im of (fileEntry?.importBindings || [])) {
11837
12002
  if (im.name !== receiver) continue;
11838
- const orig = String(im.module || '').split('::').pop();
12003
+ // fix #353: C# `using BH = Beta.Helper` splits on `.`
12004
+ const orig = String(im.module || '').split(/::|\./).pop();
11839
12005
  if (orig && orig !== receiver && typeKindsOf(orig).length > 0) {
11840
12006
  receiver = orig;
11841
12007
  typeDefs = typeKindsOf(orig);
@@ -11877,9 +12043,30 @@ function _calleeTypeQualifiedReceiver(index, def, fileEntry, call, language) {
11877
12043
  _normalizedAliasBase(index, d)));
11878
12044
  if (bases.size === 1) candidateTypes.push(bases.values().next().value);
11879
12045
  }
11880
- const symbols = index.symbols.get(call.name) || [];
12046
+ const allSymbols = index.symbols.get(call.name) || [];
11881
12047
  const isCallable = (s) => !NON_CALLABLE_TYPES.has(s.type) ||
11882
12048
  (s.type === 'field' && s.fieldType && /^func\b/.test(s.fieldType));
12049
+ // fix #353: the qualifier that is right there in the call owns the type —
12050
+ // `beta.Helper.widget()` (package), `Beta.Helper.Widget()` (namespace),
12051
+ // `using BH = Beta.Helper; BH.Widget()` (alias binding), `import
12052
+ // beta.Helper;` (name binding). Same-name types in other packages/
12053
+ // namespaces leave the candidate set BEFORE member-group construction
12054
+ // (the group dedupes identical signatures, so the first same-name type
12055
+ // used to swallow the second). An unresolvable qualifier keeps them all.
12056
+ const ownerFiles = (language === 'java' || language === 'csharp')
12057
+ ? _qualifiedStaticOwnerFiles(index, fileEntry, call, receiver, language, def)
12058
+ : null;
12059
+ // A package qualifier the resolver cannot place (`org.external.Helper`)
12060
+ // over a project type of the same name: unpinnable — visible, never
12061
+ // confirmed by first-definition selection (#206 discipline).
12062
+ if (language === 'java' && call.receiverIsTypeQualified &&
12063
+ call.receiverTypeQualifier && /^[a-z_]/.test(call.receiverTypeQualifier) &&
12064
+ !ownerFiles) {
12065
+ return { unverified: 'method-ambiguous' };
12066
+ }
12067
+ const symbols = ownerFiles
12068
+ ? allSymbols.filter(s => !candidateTypes.includes(s.className) || ownerFiles.has(s.file))
12069
+ : allSymbols;
11883
12070
  if (language === 'java' || language === 'csharp') {
11884
12071
  // Class-qualified Java/C# calls see the compiler member group on the
11885
12072
  // qualifier. The helper handles inherited slots and C# name hiding.
package/core/project.js CHANGED
@@ -1946,7 +1946,7 @@ class ProjectIndex {
1946
1946
  * the usage scan saw (and may have deliberately dropped), as opposed to
1947
1947
  * JSX children, HTML markup, or other non-code text (fix #350).
1948
1948
  */
1949
- isIdentifierAtPosition(content, lineNum, column, filePath) {
1949
+ isIdentifierAtPosition(content, lineNum, column, filePath, name) {
1950
1950
  const language = detectLanguage(filePath, this.root);
1951
1951
  if (!language) return false;
1952
1952
  try {
@@ -1954,7 +1954,12 @@ class ProjectIndex {
1954
1954
  safeParse(getParser(language), content);
1955
1955
  if (!tree) return false;
1956
1956
  const node = tree.rootNode.descendantForPosition({ row: lineNum - 1, column });
1957
- return !!node && /identifier|^name$|^word$/.test(node.type);
1957
+ if (!node || !/identifier|^name$|^word$/.test(node.type)) return false;
1958
+ // fix #352: the identifier node must BE the name — a hyphenated JSX
1959
+ // attribute (`data-cell-state`) is one property_identifier whose
1960
+ // text merely contains `cell`; the ACCOUNT counts that line as
1961
+ // other-text, so usages must list it (the #350 equality).
1962
+ return node.text === name;
1958
1963
  } catch (e) {
1959
1964
  return false;
1960
1965
  }
package/core/search.js CHANGED
@@ -84,7 +84,7 @@ function appendTextComplements(index, {
84
84
  // the AST scan classified and then deliberately dropped (Rust enum
85
85
  // variants against a struct pin, #234) is code, not text: skip it.
86
86
  if (!commentOrString &&
87
- index.isIdentifierAtPosition(content, lineNum, match.index, filePath)) continue;
87
+ index.isIdentifierAtPosition(content, lineNum, match.index, filePath, name)) continue;
88
88
  const usage = {
89
89
  file: filePath,
90
90
  relativePath: fileEntry.relativePath,
package/core/shared.js CHANGED
@@ -126,7 +126,7 @@ function addTestExclusions(exclude) {
126
126
  */
127
127
  function literalNameRegex(name, flags = '') {
128
128
  return new RegExp(
129
- `(?<![\\p{L}\\p{N}_$])${escapeRegExp(name)}(?![\\p{L}\\p{N}_$])`,
129
+ `(?<![\\p{L}\\p{N}_])${escapeRegExp(name)}(?![\\p{L}\\p{N}_])`,
130
130
  flags.includes('u') ? flags : flags + 'u');
131
131
  }
132
132
 
package/languages/java.js CHANGED
@@ -1502,14 +1502,34 @@ function findCallsInCode(code, parser) {
1502
1502
  const valueNode = receiverNode.childForFieldName('value');
1503
1503
  if (valueNode?.type === 'identifier') castReceiverName = valueNode.text;
1504
1504
  }
1505
- const receiver = castReceiverName ||
1505
+ let receiver = castReceiverName ||
1506
1506
  ((receiverNode?.type === 'identifier' || receiverNode?.type === 'this')
1507
1507
  ? receiverNode.text : undefined);
1508
+ // fix #353: `beta.Helper.widget()` — a lowercase dotted root
1509
+ // under a capitalized member is a PACKAGE-qualified type
1510
+ // (Java packages are lowercase by convention, types
1511
+ // capitalized); the receiver is the type and the package is
1512
+ // its qualifier, so same-name static methods in two packages
1513
+ // resolve by the qualifier that is right there in the call.
1514
+ let packageQualifier;
1515
+ if (!receiver && receiverNode?.type === 'field_access') {
1516
+ const rootNode = receiverNode.childForFieldName('object');
1517
+ const fldNode = receiverNode.childForFieldName('field');
1518
+ const rootText = rootNode?.text || '';
1519
+ const rootHead = rootText.split('.')[0];
1520
+ if (fldNode?.type === 'identifier' && /^[A-Z]/.test(fldNode.text) &&
1521
+ /^[a-z_][\w]*(\.[a-z_][\w]*)*$/.test(rootText) &&
1522
+ !getReceiverType(rootHead) && !isDeclaredLocal(rootHead) &&
1523
+ !hasEnclosingField(node, rootHead)) {
1524
+ receiver = fldNode.text;
1525
+ packageQualifier = rootText;
1526
+ }
1527
+ }
1508
1528
  const receiverType = castReceiverType ||
1509
1529
  ((receiver && receiver !== 'this') ? getReceiverType(receiver) : undefined);
1510
- const receiverTypeQualifier = !castReceiverType && receiver
1511
- ? getReceiverTypeQualifier(receiver) : undefined;
1512
- const receiverIsTypeQualified = !!(receiverNode?.type === 'identifier' &&
1530
+ const receiverTypeQualifier = packageQualifier ||
1531
+ (!castReceiverType && receiver ? getReceiverTypeQualifier(receiver) : undefined);
1532
+ const receiverIsTypeQualified = !!((receiverNode?.type === 'identifier' || packageQualifier) &&
1513
1533
  receiver && /^[A-Z]/.test(receiver) && !receiverType &&
1514
1534
  !isDeclaredLocal(receiver) && !hasEnclosingField(node, receiver));
1515
1535
  // fix #202: one-hop declared-field receivers —
package/languages/rust.js CHANGED
@@ -3152,6 +3152,14 @@ function findImportsInCode(code, parser) {
3152
3152
  if (pathNode && aliasNode) {
3153
3153
  addLeaf(joinUsePath(prefix, pathNode.text), aliasNode.text,
3154
3154
  'use', false, line);
3155
+ // fix #353: `use alpha::widget as renamed; renamed()` — the
3156
+ // alias pairing feeds findCallers' import-rename surface
3157
+ // (calledAs), exactly like Python/JS `import x as y`.
3158
+ const original = String(pathNode.text).split('::').pop();
3159
+ if (original && original !== aliasNode.text && original !== 'self') {
3160
+ if (!imports.aliases) imports.aliases = [];
3161
+ imports.aliases.push({ original, local: aliasNode.text });
3162
+ }
3155
3163
  }
3156
3164
  return;
3157
3165
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ucn",
3
- "version": "5.3.2",
3
+ "version": "5.3.3",
4
4
  "mcpName": "io.github.mleoca/ucn",
5
5
  "description": "Auditable AST code intelligence for AI agents: 18 task-oriented commands through one MCP tool, CLI, or agent skill. Supports JS/TS, Python, Go, Rust, Java, C, C++, C#, and HTML.",
6
6
  "main": "index.js",