ucn 5.3.4 → 5.3.6

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/languages/rust.js CHANGED
@@ -5,6 +5,9 @@
5
5
  * modules, macros, and const/static declarations.
6
6
  */
7
7
 
8
+ const { ReceiverTypeMap, typeOrigin } = require('./type-evidence');
9
+
10
+
8
11
  const {
9
12
  traverseTree,
10
13
  traverseTreeCached,
@@ -916,6 +919,14 @@ function _processClass(node, types, processedRanges, lines, code) {
916
919
  members: [],
917
920
  modifiers: visibility ? [visibility] : [],
918
921
  ...(aliasOf && { aliasOf }),
922
+ aliasTypeText: node.childForFieldName('type')?.text,
923
+ aliasTypeParameters: (node.childForFieldName('type_parameters')?.namedChildren || [])
924
+ .map(parameter => parameter.type === 'type_identifier' ? parameter.text
925
+ : parameter.childForFieldName('name')?.text ||
926
+ parameter.namedChildren.find(child => child.type === 'type_identifier')?.text)
927
+ .map(name => name || null),
928
+ aliasTypeDefaults: (node.childForFieldName('type_parameters')?.namedChildren || [])
929
+ .map(parameter => parameter.childForFieldName('default_type')?.text || null),
919
930
  ...(docstring && { docstring })
920
931
  });
921
932
  }
@@ -1525,12 +1536,57 @@ function _tokenTreeCallArgsAfter(children, nameIndex) {
1525
1536
  return args?.type === 'token_tree' && args.text.startsWith('(') ? args : null;
1526
1537
  }
1527
1538
 
1539
+ // Extract the base type name from a Rust type node (strips &, &mut, Box<>, etc.)
1540
+ function extractTypeName(typeNode) {
1541
+ if (!typeNode) return null;
1542
+ if (typeNode.type === 'type_identifier' ||
1543
+ typeNode.type === 'primitive_type') {
1544
+ return typeNode.text;
1545
+ }
1546
+ if (typeNode.type === 'reference_type') {
1547
+ // &Filter or &mut Filter -> Filter
1548
+ for (let i = 0; i < typeNode.namedChildCount; i++) {
1549
+ const r = extractTypeName(typeNode.namedChild(i));
1550
+ if (r) return r;
1551
+ }
1552
+ }
1553
+ if (typeNode.type === 'abstract_type' || typeNode.type === 'dynamic_type') {
1554
+ for (let i = 0; i < typeNode.namedChildCount; i++) {
1555
+ const r = extractTypeName(typeNode.namedChild(i));
1556
+ if (r) return r;
1557
+ }
1558
+ }
1559
+ if (typeNode.type === 'generic_type') {
1560
+ // Box<Filter> -> Filter (or get the outer type)
1561
+ return extractTypeName(typeNode.namedChild(0));
1562
+ }
1563
+ if (typeNode.type === 'scoped_type_identifier') {
1564
+ // module::Type -> Type
1565
+ const nameNode = typeNode.childForFieldName('name');
1566
+ return nameNode?.text || null;
1567
+ }
1568
+ return null;
1569
+ }
1570
+
1571
+
1572
+ // Shared by ordinary AST calls and macro token-tree calls. Both carry the
1573
+ // same self-field owner identity; a field spelling is never a local receiver.
1574
+ function findEnclosingImplType(node) {
1575
+ for (let parent = node.parent; parent; parent = parent.parent) {
1576
+ if (parent.type === 'impl_item') {
1577
+ const type = parent.childForFieldName('type');
1578
+ return (type && extractTypeName(type)) || undefined;
1579
+ }
1580
+ }
1581
+ return undefined;
1582
+ }
1583
+
1528
1584
  function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverType,
1529
1585
  isPatternShadow, isFlowInvalidated, context = 'invocation') {
1530
1586
  const contextKind = typeof context === 'string' ? context : context.kind;
1531
1587
  const containerMacro = typeof context === 'object' ? context.containerMacro : undefined;
1532
1588
  const inheritedTokenTypes = typeof context === 'object' && context.tokenTypes
1533
- ? context.tokenTypes : new Map();
1589
+ ? context.tokenTypes : new ReceiverTypeMap();
1534
1590
  const children = [];
1535
1591
  for (let i = 0; i < tree.childCount; i++) children.push(tree.child(i));
1536
1592
  // Macro arguments are token trees, so a typed closure parameter is not a
@@ -1544,7 +1600,7 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
1544
1600
  let close = i + 1;
1545
1601
  while (close < children.length && children[close].type !== '|') close++;
1546
1602
  if (close >= children.length) break;
1547
- const bindings = new Map(inheritedTokenTypes);
1603
+ const bindings = new ReceiverTypeMap(inheritedTokenTypes);
1548
1604
  let cursor = i + 1;
1549
1605
  while (cursor < close) {
1550
1606
  const nameNode = children[cursor];
@@ -1562,7 +1618,7 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
1562
1618
  if (simplePath) {
1563
1619
  const identifiers = typeTokens.filter(token => token.type === 'identifier');
1564
1620
  if (identifiers.length > 0) {
1565
- bindings.set(nameNode.text, identifiers[identifiers.length - 1].text);
1621
+ bindings.set(nameNode.text, identifiers[identifiers.length - 1].text, 'annotation', nameNode);
1566
1622
  }
1567
1623
  }
1568
1624
  cursor = end + 1;
@@ -1703,12 +1759,15 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
1703
1759
  ? ({ string_literal: 'str', raw_string_literal: 'str',
1704
1760
  char_literal: 'char', boolean_literal: 'bool' })[recvTok.type]
1705
1761
  : undefined;
1706
- const receiverType = (receiver && receiver !== 'self')
1762
+ const receiverRootType = receiverRoot === 'self'
1763
+ ? findEnclosingImplType(tok) : getReceiverType?.(receiverRoot, tok);
1764
+ const receiverType = receiverField ? undefined : (receiver && receiver !== 'self')
1707
1765
  ? (getReceiverType?.(receiver, tok) || inheritedTokenTypes.get(receiver))
1708
1766
  : litType;
1709
1767
  const receiverPatternShadow = !!(receiver && isPatternShadow?.(tok, receiver));
1710
1768
  const receiverFlowInvalidated = !!(receiver && isFlowInvalidated?.(tok, receiver));
1711
1769
  const iterationSource = rustIterationSourceOf(tok, receiver);
1770
+ const patternSource = rustPatternBindingOf(tok, receiver);
1712
1771
  const producer = !receiver && lastProducer &&
1713
1772
  lastProducer.callEnd === recvTok?.endIndex ? lastProducer : null;
1714
1773
  const record = {
@@ -1719,10 +1778,12 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
1719
1778
  isMethod: true,
1720
1779
  receiver: receiverField ? undefined : receiver,
1721
1780
  ...(receiverField && { receiverRoot, receiverField }),
1722
- ...(receiverType && { receiverType }),
1781
+ ...(receiverRootType && { receiverRootType }),
1782
+ ...(receiverType && { receiverType, ...(getReceiverType?.(receiver, tok, true) || inheritedTokenTypes.fields(receiver, receiverType)) }),
1723
1783
  ...(receiverPatternShadow && { receiverPatternShadow: true }),
1724
1784
  ...(receiverFlowInvalidated && { receiverFlowInvalidated: true }),
1725
1785
  ...(iterationSource || {}),
1786
+ ...(patternSource || {}),
1726
1787
  ...(producer && {
1727
1788
  receiverCall: producer.name,
1728
1789
  ...(producer.isMethod && { receiverCallIsMethod: true }),
@@ -1948,26 +2009,13 @@ function rustPatternBindingOf(node, receiver) {
1948
2009
  return identifiers.length === 1 ? identifiers[0] : null;
1949
2010
  };
1950
2011
 
1951
- for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) {
1952
- if (ancestor.type === 'function_item') break;
1953
- if (ancestor.type === 'closure_expression') {
1954
- const params = ancestor.childForFieldName('parameters');
1955
- if (params && patternContainsIdentifier(params, receiver)) break;
1956
- continue; // captured binding from an outer match arm
1957
- }
1958
- if (ancestor.type !== 'match_arm') continue;
1959
- let matchExpression = ancestor.parent;
1960
- while (matchExpression && matchExpression.type !== 'match_expression' &&
1961
- matchExpression.type !== 'function_item') {
1962
- matchExpression = matchExpression.parent;
1963
- }
1964
- const matchValue = matchExpression?.type === 'match_expression'
1965
- ? matchExpression.childForFieldName('value')
1966
- : null;
2012
+ const readPattern = (root, matchValue) => {
1967
2013
  const source = matchValue?.type === 'identifier'
1968
2014
  ? { receiverPatternSourceVariable: matchValue.text }
2015
+ : matchValue?.type === 'call_expression'
2016
+ ? { receiverPatternSourceCallStart: matchValue.startIndex,
2017
+ receiverPatternSourceCallEnd: matchValue.endIndex }
1969
2018
  : {};
1970
- const root = ancestor.childForFieldName('pattern');
1971
2019
  const pending = root ? [root] : [];
1972
2020
  while (pending.length > 0) {
1973
2021
  const pattern = pending.pop();
@@ -1976,7 +2024,19 @@ function rustPatternBindingOf(node, receiver) {
1976
2024
  const positional = pattern.namedChildren
1977
2025
  .filter(child => !typeNode || child.id !== typeNode.id);
1978
2026
  for (let i = 0; i < positional.length; i++) {
1979
- if (directBindingName(positional[i]) !== receiver) continue;
2027
+ const tupleProjection = (part, steps = []) => {
2028
+ if (directBindingName(part) === receiver) return steps;
2029
+ if (part.type !== 'tuple_pattern') return null;
2030
+ // Unnamed `_` nodes still occupy a tuple position.
2031
+ const elements = part.children.filter(child => !['(', ')', ','].includes(child.type));
2032
+ for (const [position, child] of elements.entries()) {
2033
+ const found = tupleProjection(child, [...steps, position]);
2034
+ if (found) return found;
2035
+ }
2036
+ return null;
2037
+ };
2038
+ const projection = tupleProjection(positional[i]);
2039
+ if (!projection) continue;
1980
2040
  const pathText = typeNode?.text;
1981
2041
  if (!pathText) return null;
1982
2042
  const segments = pathText.split('::').filter(Boolean);
@@ -1985,6 +2045,7 @@ function rustPatternBindingOf(node, receiver) {
1985
2045
  return {
1986
2046
  receiverPatternVariant: variant,
1987
2047
  receiverPatternIndex: i,
2048
+ ...(projection.length && { receiverPatternProjection: projection }),
1988
2049
  ...source,
1989
2050
  ...(segments.length > 0 && {
1990
2051
  receiverPatternOwner: segments.join('::'),
@@ -1997,6 +2058,49 @@ function rustPatternBindingOf(node, receiver) {
1997
2058
  }
1998
2059
  }
1999
2060
  return null;
2061
+ };
2062
+
2063
+ for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) {
2064
+ if (ancestor.type === 'function_item') break;
2065
+ if (ancestor.type === 'block') {
2066
+ for (const child of [...ancestor.namedChildren].reverse()) {
2067
+ if (child.endIndex > node.startIndex) continue;
2068
+ const declaration = child.type === 'expression_statement' ? child.namedChild(0) : child;
2069
+ const pattern = declaration?.type === 'let_declaration'
2070
+ ? declaration.childForFieldName('pattern')
2071
+ : declaration?.type === 'assignment_expression'
2072
+ ? declaration.childForFieldName('left') : null;
2073
+ if (patternContainsIdentifier(pattern, receiver)) {
2074
+ return declaration.type === 'let_declaration' && declaration.childForFieldName('alternative')
2075
+ ? readPattern(pattern, declaration.childForFieldName('value')) : null;
2076
+ }
2077
+ }
2078
+ }
2079
+ if (ancestor.type === 'for_expression' &&
2080
+ patternContainsIdentifier(ancestor.childForFieldName('pattern'), receiver)) return null;
2081
+ if (ancestor.type === 'closure_expression') {
2082
+ const params = ancestor.childForFieldName('parameters');
2083
+ if (params && patternContainsIdentifier(params, receiver)) break;
2084
+ continue; // captured binding from an outer match arm
2085
+ }
2086
+ let matchValue, root;
2087
+ if (ancestor.type === 'match_arm') {
2088
+ let matchExpression = ancestor.parent;
2089
+ while (matchExpression && matchExpression.type !== 'match_expression' &&
2090
+ matchExpression.type !== 'function_item') matchExpression = matchExpression.parent;
2091
+ matchValue = matchExpression?.type === 'match_expression'
2092
+ ? matchExpression.childForFieldName('value') : null;
2093
+ root = ancestor.childForFieldName('pattern');
2094
+ } else if (['if_expression', 'while_expression'].includes(ancestor.type)) {
2095
+ const body = ancestor.childForFieldName(ancestor.type === 'if_expression' ? 'consequence' : 'body');
2096
+ const condition = ancestor.childForFieldName('condition');
2097
+ if (!body || node.startIndex < body.startIndex || node.endIndex > body.endIndex ||
2098
+ condition?.type !== 'let_condition') continue;
2099
+ matchValue = condition.childForFieldName('value');
2100
+ root = condition.childForFieldName('pattern');
2101
+ } else continue;
2102
+ if (!patternContainsIdentifier(root, receiver)) continue;
2103
+ return readPattern(root, matchValue);
2000
2104
  }
2001
2105
  return null;
2002
2106
  }
@@ -2067,38 +2171,6 @@ function findCallsInCode(code, parser) {
2067
2171
  return ['function_item', 'closure_expression'].includes(node.type);
2068
2172
  };
2069
2173
 
2070
- // Extract the base type name from a Rust type node (strips &, &mut, Box<>, etc.)
2071
- const extractTypeName = (typeNode) => {
2072
- if (!typeNode) return null;
2073
- if (typeNode.type === 'type_identifier' ||
2074
- typeNode.type === 'primitive_type') {
2075
- return typeNode.text;
2076
- }
2077
- if (typeNode.type === 'reference_type') {
2078
- // &Filter or &mut Filter -> Filter
2079
- for (let i = 0; i < typeNode.namedChildCount; i++) {
2080
- const r = extractTypeName(typeNode.namedChild(i));
2081
- if (r) return r;
2082
- }
2083
- }
2084
- if (typeNode.type === 'abstract_type' || typeNode.type === 'dynamic_type') {
2085
- for (let i = 0; i < typeNode.namedChildCount; i++) {
2086
- const r = extractTypeName(typeNode.namedChild(i));
2087
- if (r) return r;
2088
- }
2089
- }
2090
- if (typeNode.type === 'generic_type') {
2091
- // Box<Filter> -> Filter (or get the outer type)
2092
- return extractTypeName(typeNode.namedChild(0));
2093
- }
2094
- if (typeNode.type === 'scoped_type_identifier') {
2095
- // module::Type -> Type
2096
- const nameNode = typeNode.childForFieldName('name');
2097
- return nameNode?.text || null;
2098
- }
2099
- return null;
2100
- };
2101
-
2102
2174
  const extractTypeQualifier = (typeNode) => {
2103
2175
  if (!typeNode) return null;
2104
2176
  if (typeNode.type === 'reference_type') {
@@ -2120,7 +2192,7 @@ function findCallsInCode(code, parser) {
2120
2192
 
2121
2193
  // Build type map from function parameters (including self receiver for impl methods)
2122
2194
  const buildScopeTypeMap = (node) => {
2123
- const typeMap = new Map();
2195
+ const typeMap = new ReceiverTypeMap();
2124
2196
  typeMap.qualifiers = new Map();
2125
2197
  typeMap.iteratorItems = new Map();
2126
2198
  typeMap.annotationTexts = new Map();
@@ -2154,7 +2226,7 @@ function findCallsInCode(code, parser) {
2154
2226
  // Pattern can be identifier or _
2155
2227
  const name = patternNode.type === 'identifier' ? patternNode.text : null;
2156
2228
  if (name) {
2157
- typeMap.set(name, typeName);
2229
+ typeMap.set(name, typeName, 'annotation', param);
2158
2230
  typeMap.annotationTexts.set(name, typeNode.text);
2159
2231
  if (qualifier) typeMap.qualifiers.set(name, qualifier);
2160
2232
  if (iteratorItem) typeMap.iteratorItems.set(name, iteratorItem);
@@ -2253,13 +2325,21 @@ function findCallsInCode(code, parser) {
2253
2325
  !!flowEventAt(node, varName)?.invalidated;
2254
2326
 
2255
2327
  // Look up variable type from scope chain
2256
- const getReceiverType = (varName, atNode) => {
2328
+ const getReceiverType = (varName, atNode, evidence = false) => {
2257
2329
  if (atNode && patternShadowsAt(atNode, varName)) return undefined;
2258
2330
  const flow = flowEventAt(atNode, varName);
2259
- if (flow?.type) return flow.type;
2331
+ if (flow?.type) {
2332
+ const { until, ...origin } = flow;
2333
+ return evidence ? { receiverTypeSource: 'flow',
2334
+ receiverTypeEvidence: { ...typeOrigin('flow', atNode), ...origin,
2335
+ ...(Number.isFinite(until) && { until }) },
2336
+ ...(flow.qualifier && { receiverTypeQualifier: flow.qualifier }) } : flow.type;
2337
+ }
2260
2338
  for (let i = functionStack.length - 1; i >= 0; i--) {
2261
2339
  const typeMap = scopeTypes.get(functionStack[i].startLine);
2262
- if (typeMap?.has(varName)) return typeMap.get(varName);
2340
+ if (typeMap?.has(varName)) return evidence ? { ...typeMap.fields(varName),
2341
+ ...(typeMap.qualifiers?.has(varName) && { receiverTypeQualifier: typeMap.qualifiers.get(varName) }) }
2342
+ : typeMap.get(varName);
2263
2343
  if (typeMap?.boundNames?.has(varName)) return undefined;
2264
2344
  }
2265
2345
  return undefined;
@@ -2363,15 +2443,31 @@ function findCallsInCode(code, parser) {
2363
2443
  };
2364
2444
  };
2365
2445
 
2366
- // Walk up to the enclosing impl block's target type (impl<T> Foo<T> → Foo).
2367
- const findEnclosingImplType = (n) => {
2368
- for (let p = n.parent; p; p = p.parent) {
2369
- if (p.type === 'impl_item') {
2370
- const t = p.childForFieldName('type');
2371
- return (t && extractTypeName(t)) || undefined;
2372
- }
2373
- }
2374
- return undefined;
2446
+ const copiedBindingType = (value, assignment, retainedName = null) => {
2447
+ let expression = value;
2448
+ while (expression && ['reference_expression', 'parenthesized_expression'].includes(expression.type)) {
2449
+ expression = expression.childForFieldName('value') || expression.namedChildren.at(-1);
2450
+ }
2451
+ const dereferenced = !retainedName && expression?.type === 'unary_expression' && expression.child(0)?.text === '*';
2452
+ if (dereferenced) expression = expression.namedChild(0);
2453
+ const name = retainedName || (expression?.type === 'identifier' ? expression.text : null);
2454
+ if (!name) return null;
2455
+ const type = getReceiverType(name, assignment);
2456
+ const facts = type && getReceiverType(name, assignment, true);
2457
+ if (!facts?.receiverTypeEvidence || ['unknown', 'guess'].includes(facts.receiverTypeSource)) return null;
2458
+ const annotation = dereferenced && getReceiverAnnotationText(name, assignment);
2459
+ // Deref on a custom owner can change the type. Only an actual
2460
+ // reference annotation proves the built-in `&*reference` reborrow.
2461
+ if (dereferenced && (facts.receiverTypeSource !== 'annotation' || !annotation?.trim().startsWith('&'))) return null;
2462
+ const qualifier = getReceiverTypeQualifier(name, assignment);
2463
+ return { type, ...(qualifier && { qualifier }), aliasBinding: {
2464
+ variable: name, target: assignment.childForFieldName(
2465
+ assignment.type === 'let_declaration' ? 'pattern' : 'left')?.text,
2466
+ type, origin: facts.receiverTypeEvidence,
2467
+ assignment: typeOrigin('flow', assignment),
2468
+ ...(dereferenced && { referenceAnnotation: annotation }),
2469
+ kind: retainedName ? 'reassignment' : value.type === 'identifier' ? 'copy' : 'borrow',
2470
+ } };
2375
2471
  };
2376
2472
 
2377
2473
  const closureContractSource = (node) => {
@@ -2495,7 +2591,11 @@ function findCallsInCode(code, parser) {
2495
2591
  at: node.endIndex,
2496
2592
  until,
2497
2593
  ...(() => {
2498
- const inferred = matchBindingType(value, node);
2594
+ // Rust assignment preserves a variable's static
2595
+ // type; a fresh `let` may shadow it with another.
2596
+ const inferred = copiedBindingType(value, node,
2597
+ node.type === 'assignment_expression' ? pattern.text : null) ||
2598
+ matchBindingType(value, node);
2499
2599
  return inferred
2500
2600
  ? { invalidated: false, ...inferred }
2501
2601
  : { invalidated: !valueHasFlowProducer(value) };
@@ -2761,7 +2861,7 @@ function findCallsInCode(code, parser) {
2761
2861
  callEnd: node.endIndex,
2762
2862
  isMethod: true,
2763
2863
  receiver,
2764
- ...(receiverType && { receiverType }),
2864
+ ...(receiverType && { receiverType, ...(getReceiverType(receiver, node, true) || { receiverTypeSource: 'unknown' }) }),
2765
2865
  ...(receiverTypeQualifier && { receiverTypeQualifier }),
2766
2866
  ...(receiverIteratorItemType && { receiverIteratorItemType }),
2767
2867
  ...(receiverPatternShadow && { receiverPatternShadow: true }),
@@ -2987,7 +3087,7 @@ function findCallsInCode(code, parser) {
2987
3087
  line: fieldNode.startPosition.row + 1,
2988
3088
  isMethod: true,
2989
3089
  receiver,
2990
- ...(receiverType && { receiverType }),
3090
+ ...(receiverType && { receiverType, ...(getReceiverType(receiver, node, true) || { receiverTypeSource: 'unknown' }) }),
2991
3091
  ...(receiverPatternShadow && { receiverPatternShadow: true }),
2992
3092
  ...(receiverFlowInvalidated && { receiverFlowInvalidated: true }),
2993
3093
  isFunctionReference: true,
@@ -3061,7 +3161,7 @@ function findCallsInCode(code, parser) {
3061
3161
  }
3062
3162
  }
3063
3163
  if (typeName) {
3064
- typeMap.set(varName, typeName);
3164
+ typeMap.set(varName, typeName, typeAnnotation ? 'annotation' : valueNode?.type === 'call_expression' ? 'guess' : 'constructor', typeAnnotation || valueNode);
3065
3165
  if (typeQualifier) typeMap.qualifiers.set(varName, typeQualifier);
3066
3166
  }
3067
3167
  }
@@ -3121,7 +3221,7 @@ function findImportsInCode(code, parser) {
3121
3221
  const right = String(suffix || '').replace(/^::/, '');
3122
3222
  return left && right ? `${left}::${right}` : left || right;
3123
3223
  };
3124
- const addLeaf = (module, localName, type = 'use', dynamic = false, line) => {
3224
+ const addLeaf = (module, localName, type = 'use', dynamic = false, line, rename = null) => {
3125
3225
  if (!module || !localName) return;
3126
3226
  imports.push({
3127
3227
  module,
@@ -3129,6 +3229,7 @@ function findImportsInCode(code, parser) {
3129
3229
  type,
3130
3230
  dynamic,
3131
3231
  line,
3232
+ ...(rename && { renames: [rename] }),
3132
3233
  });
3133
3234
  };
3134
3235
  const collectUseTree = (node, prefix, line) => {
@@ -3150,13 +3251,20 @@ function findImportsInCode(code, parser) {
3150
3251
  const pathNode = node.namedChild(0);
3151
3252
  const aliasNode = node.childForFieldName('alias') || node.namedChild(1);
3152
3253
  if (pathNode && aliasNode) {
3153
- addLeaf(joinUsePath(prefix, pathNode.text), aliasNode.text,
3154
- 'use', false, line);
3155
3254
  // fix #353: `use alpha::widget as renamed; renamed()` — the
3156
3255
  // alias pairing feeds findCallers' import-rename surface
3157
3256
  // (calledAs), exactly like Python/JS `import x as y`.
3257
+ // fix #357: `renames` pairs the alias with ITS record so
3258
+ // createImportBindings emits {name: original, alias: local}
3259
+ // (the #348 Python shape); two renames of one source name
3260
+ // from different modules each pair with their own module
3261
+ // instead of both bindings matching both calls. `names`
3262
+ // keeps the local spelling (parser contract, imports command).
3158
3263
  const original = String(pathNode.text).split('::').pop();
3159
- if (original && original !== aliasNode.text && original !== 'self') {
3264
+ const renamed = original && original !== aliasNode.text && original !== 'self';
3265
+ addLeaf(joinUsePath(prefix, pathNode.text), aliasNode.text,
3266
+ 'use', false, line, renamed ? { original, local: aliasNode.text } : null);
3267
+ if (renamed) {
3160
3268
  if (!imports.aliases) imports.aliases = [];
3161
3269
  imports.aliases.push({ original, local: aliasNode.text });
3162
3270
  }
@@ -0,0 +1,63 @@
1
+ 'use strict';
2
+
3
+ /** Data-only AST witness. Never retain a tree-sitter node in a call cache. */
4
+ function typeOrigin(source = 'unknown', node = null) {
5
+ return {
6
+ source,
7
+ ...(node && {
8
+ line: node.startPosition.row + 1,
9
+ column: node.startPosition.column,
10
+ start: node.startIndex,
11
+ end: node.endIndex,
12
+ nodeType: node.type,
13
+ }),
14
+ };
15
+ }
16
+
17
+ /**
18
+ * A string-valued type map with independent provenance. Existing inference
19
+ * keeps its Map semantics; adding witnesses must not change classification.
20
+ * Reassignment, deletion, and scope restoration update the witness together
21
+ * with the inferred type so a stale constructor can never justify a new value.
22
+ */
23
+ class ReceiverTypeMap extends Map {
24
+ constructor(entries) {
25
+ super();
26
+ this.origins = new Map();
27
+ if (entries) this.restore(entries);
28
+ }
29
+
30
+ set(name, type, source = 'unknown', node = null) {
31
+ super.set(name, type);
32
+ this.origins.set(name, typeof source === 'object' ? { ...source } : typeOrigin(source, node));
33
+ return this;
34
+ }
35
+
36
+ delete(name) {
37
+ this.origins.delete(name);
38
+ return super.delete(name);
39
+ }
40
+
41
+ clear() {
42
+ this.origins.clear();
43
+ super.clear();
44
+ }
45
+
46
+ restore(entries) {
47
+ this.clear();
48
+ for (const [name, type] of entries) {
49
+ this.set(name, type, entries.origins?.get(name) || 'unknown');
50
+ }
51
+ return this;
52
+ }
53
+
54
+ fields(name, type = this.get(name)) {
55
+ const origin = type && type === this.get(name) ? this.origins.get(name) : null;
56
+ return {
57
+ receiverTypeSource: origin?.source || 'unknown',
58
+ ...(origin && { receiverTypeEvidence: { ...origin, name, type } }),
59
+ };
60
+ }
61
+ }
62
+
63
+ module.exports = { ReceiverTypeMap, typeOrigin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ucn",
3
- "version": "5.3.4",
3
+ "version": "5.3.6",
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",
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "scripts": {
12
12
  "version": "node scripts/sync-server-version.js && git add server.json",
13
- "test": "node --test test/shell-output.test.js test/parser-unit.test.js test/integration.test.js test/cache.test.js test/formatter.test.js test/interactive.test.js test/feature.test.js test/regression-js.test.js test/regression-py.test.js test/regression-go.test.js test/regression-java.test.js test/regression-rust.test.js test/regression-c-family.test.js test/regression-cross.test.js test/regression-mcp.test.js test/mcp-protocol.test.js test/mcp-sdk-compat.test.js test/dependency-security.test.js test/regression-parser.test.js test/regression-commands.test.js test/regression-fixes.test.js test/regression-bugfixes.test.js test/release-surface-regressions.test.js test/prerelease-audit.test.js test/release-readiness-audit.test.js test/cross-language.test.js test/accuracy.test.js test/command-coverage.test.js test/perf-optimizations.test.js test/performance-gate-policy.test.js test/oracle-gate-policy.test.js test/outcome-policy.test.js test/consistency-eval.test.js test/agent-public-surface-benchmark.test.js test/command-contracts.test.js test/language-adapter.test.js test/systematic-test.js test/mcp-edge-cases.js test/conservation.test.js test/parity-test.js test/trust-matrix.test.js",
13
+ "test": "node --test test/evidence-provenance.test.js test/provenance-unit.test.js test/shell-output.test.js test/parser-unit.test.js test/integration.test.js test/cache.test.js test/formatter.test.js test/interactive.test.js test/feature.test.js test/regression-js.test.js test/regression-py.test.js test/regression-go.test.js test/regression-java.test.js test/regression-rust.test.js test/regression-c-family.test.js test/regression-cross.test.js test/regression-mcp.test.js test/mcp-protocol.test.js test/mcp-sdk-compat.test.js test/dependency-security.test.js test/regression-parser.test.js test/regression-commands.test.js test/regression-fixes.test.js test/regression-bugfixes.test.js test/release-surface-regressions.test.js test/prerelease-audit.test.js test/release-readiness-audit.test.js test/cross-language.test.js test/accuracy.test.js test/command-coverage.test.js test/perf-optimizations.test.js test/performance-gate-policy.test.js test/oracle-gate-policy.test.js test/outcome-policy.test.js test/consistency-eval.test.js test/agent-public-surface-benchmark.test.js test/command-contracts.test.js test/language-adapter.test.js test/systematic-test.js test/mcp-edge-cases.js test/conservation.test.js test/parity-test.js test/trust-matrix.test.js",
14
14
  "benchmark:agent": "node test/agent-public-surface-benchmark.js",
15
15
  "benchmark:agent:gate": "node test/agent-public-surface-benchmark.js --gate",
16
16
  "benchmark:agent:legacy": "node test/agent-understanding-benchmark.js",