ucn 5.2.1 → 5.2.2

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/go.js CHANGED
@@ -653,6 +653,8 @@ const GO_BUILTINS = new Set([
653
653
  * target with its declared return position)
654
654
  * y = q() → { assignedTo: 'y' } (plain `=` only — `+=` etc.
655
655
  * don't bind the call's type to the variable)
656
+ * var z = q() → { assignedTo: 'z' } (`var` declarations use
657
+ * repeated AST `name` fields plus a `value` list)
656
658
  * a, b := g(), h() → parallel assignment: each call pairs with its
657
659
  * own LHS position, single-value semantics
658
660
  * Identifier targets only; blank (`_`) targets return undefined.
@@ -669,18 +671,25 @@ function goAssignmentTargetOf(callNode) {
669
671
  }
670
672
  n = p; p = n.parent;
671
673
  }
672
- if (!p || (p.type !== 'short_var_declaration' && p.type !== 'assignment_statement')) return undefined;
674
+ if (!p || (p.type !== 'short_var_declaration' &&
675
+ p.type !== 'assignment_statement' && p.type !== 'var_spec')) return undefined;
673
676
  if (p.type === 'assignment_statement') {
674
677
  const op = p.childForFieldName('operator');
675
678
  if (op && op.text !== '=') return undefined;
676
679
  }
677
- const right = p.childForFieldName('right');
680
+ const isVarSpec = p.type === 'var_spec';
681
+ const right = p.childForFieldName(isVarSpec ? 'value' : 'right');
678
682
  if (!right || right.id !== n.id) return undefined;
679
- const left = p.childForFieldName('left');
680
- if (!left) return undefined;
681
- const names = left.type === 'expression_list'
682
- ? Array.from({ length: left.namedChildCount }, (_, i) => left.namedChild(i))
683
- : [left];
683
+ const left = isVarSpec ? null : p.childForFieldName('left');
684
+ if (!isVarSpec && !left) return undefined;
685
+ const names = isVarSpec
686
+ ? Array.from({ length: p.childCount }, (_, i) => ({
687
+ child: p.child(i),
688
+ field: p.fieldNameForChild(i),
689
+ })).filter(({ field }) => field === 'name').map(({ child }) => child)
690
+ : left.type === 'expression_list'
691
+ ? Array.from({ length: left.namedChildCount }, (_, i) => left.namedChild(i))
692
+ : [left];
684
693
  if (rhsCount > 1) {
685
694
  const target = names[rhsIndex];
686
695
  return target?.type === 'identifier' && target.text !== '_'
@@ -762,6 +771,10 @@ function findCallsInCode(code, parser, options = {}) {
762
771
  // types are compiler-true — Go range yields the container's element.
763
772
  const scopeContainerTypes = new Map(); // scopeStartLine -> Map<name, typeText>
764
773
  const packageContainerTypes = new Map();
774
+ // Local values bound from an indexed declared field. The file parser may
775
+ // not own the receiver struct declaration, so preserve its exact root +
776
+ // field provenance for project-index resolution after all files exist.
777
+ const scopeIndexedSources = new Map();
765
778
  // Track function-typed parameter names per scope (scopeStartLine -> Set<name>)
766
779
  const funcParamScopes = new Map();
767
780
 
@@ -903,11 +916,24 @@ function findCallsInCode(code, parser, options = {}) {
903
916
  return '<anonymous>';
904
917
  };
905
918
 
906
- // Helper to get current enclosing function
907
- const getCurrentEnclosingFunction = () => {
908
- return functionStack.length > 0
909
- ? { ...functionStack[functionStack.length - 1] }
910
- : null;
919
+ // Helper to get current enclosing function. Return-flow assignments are
920
+ // keyed by function scope, so a captured receiver needs the complete
921
+ // lexical path back to the scope that binds it. Start at the binding
922
+ // scope (rather than every outer function) so a parameter/local that
923
+ // shadows the same name cannot inherit an unrelated outer flow entry.
924
+ const getCurrentEnclosingFunction = (refNode = null, name = null) => {
925
+ if (functionStack.length === 0) return null;
926
+ const allScopes = functionStack.map(scope => scope.startLine);
927
+ let scopeChain = [allScopes[allScopes.length - 1]];
928
+ if (refNode && name) {
929
+ const bindingScope = lexicalBindingScopeStart(refNode, name);
930
+ const bindingIndex = allScopes.lastIndexOf(bindingScope);
931
+ if (bindingIndex >= 0) scopeChain = allScopes.slice(bindingIndex);
932
+ }
933
+ return {
934
+ ...functionStack[functionStack.length - 1],
935
+ scopeChain,
936
+ };
911
937
  };
912
938
 
913
939
  // Resolve a local closure through the lexical function-scope chain.
@@ -931,21 +957,40 @@ function findCallsInCode(code, parser, options = {}) {
931
957
 
932
958
  // Look up variable type from scope chain
933
959
  const getReceiverType = (varName, refNode) => {
960
+ const bindingScope = refNode
961
+ ? lexicalBindingScopeStart(refNode, varName) : null;
934
962
  for (let i = functionStack.length - 1; i >= 0; i--) {
935
- const typeMap = scopeTypes.get(functionStack[i].startLine);
963
+ const scopeStart = functionStack[i].startLine;
964
+ const typeMap = scopeTypes.get(scopeStart);
936
965
  if (typeMap?.has(varName)) return typeMap.get(varName);
966
+ if (bindingScope === scopeStart) return undefined;
937
967
  }
938
968
  return refNode && isShadowedByLocal(refNode, varName)
939
969
  ? undefined : packageTypes.get(varName);
940
970
  };
941
971
  const getReceiverTypeQualifier = (varName, refNode) => {
972
+ const bindingScope = refNode
973
+ ? lexicalBindingScopeStart(refNode, varName) : null;
942
974
  for (let i = functionStack.length - 1; i >= 0; i--) {
943
- const qualifiers = scopeTypeQualifiers.get(functionStack[i].startLine);
975
+ const scopeStart = functionStack[i].startLine;
976
+ const qualifiers = scopeTypeQualifiers.get(scopeStart);
944
977
  if (qualifiers?.has(varName)) return qualifiers.get(varName);
978
+ if (bindingScope === scopeStart) return undefined;
945
979
  }
946
980
  return refNode && isShadowedByLocal(refNode, varName)
947
981
  ? undefined : packageTypeQualifiers.get(varName);
948
982
  };
983
+ const getIndexedSource = (varName, refNode) => {
984
+ const bindingScope = refNode
985
+ ? lexicalBindingScopeStart(refNode, varName) : null;
986
+ for (let i = functionStack.length - 1; i >= 0; i--) {
987
+ const scopeStart = functionStack[i].startLine;
988
+ const sources = scopeIndexedSources.get(scopeStart);
989
+ if (sources?.has(varName)) return sources.get(varName);
990
+ if (bindingScope === scopeStart) return undefined;
991
+ }
992
+ return undefined;
993
+ };
949
994
  // Compiler-true receiver type from a composite-literal receiver
950
995
  // expression (fix #298, websocket-measured — the #220(7) typing-sources
951
996
  // family): `(&Kit{...}).Run`, `(&net.Dialer{}).DialContext(...)`,
@@ -1052,6 +1097,22 @@ function findCallsInCode(code, parser, options = {}) {
1052
1097
  }
1053
1098
  return structFieldsCache;
1054
1099
  };
1100
+ const containerTypeOf = (node) => {
1101
+ if (node?.type === 'selector_expression') {
1102
+ const operand = node.childForFieldName('operand');
1103
+ const field = node.childForFieldName('field');
1104
+ if (operand?.type === 'identifier' && field &&
1105
+ !isGuessedType(operand.text)) {
1106
+ const rootType = getReceiverType(operand.text, operand);
1107
+ if (rootType) {
1108
+ return getStructFields().get(rootType)?.get(field.text) || null;
1109
+ }
1110
+ }
1111
+ } else if (node?.type === 'identifier') {
1112
+ return lookupContainerType(node.text);
1113
+ }
1114
+ return null;
1115
+ };
1055
1116
 
1056
1117
  // fix #203 (Go): is a bare-identifier function REFERENCE shadowed by an
1057
1118
  // enclosing func-literal/function parameter, method receiver, range/init
@@ -1102,13 +1163,23 @@ function findCallsInCode(code, parser, options = {}) {
1102
1163
  }
1103
1164
  return false;
1104
1165
  };
1105
- const isShadowedByLocal = (refNode, name) => {
1166
+ const lexicalBindingScopeStart = (refNode, name) => {
1167
+ const owningFunctionStart = (node) => {
1168
+ for (let current = node; current; current = current.parent) {
1169
+ if (isFunctionNode(current)) {
1170
+ return current.startPosition.row + 1;
1171
+ }
1172
+ }
1173
+ return null;
1174
+ };
1106
1175
  for (let p = refNode.parent; p; p = p.parent) {
1107
1176
  if (p.type === 'block') {
1108
1177
  for (let i = 0; i < p.namedChildCount; i++) {
1109
1178
  const stmt = p.namedChild(i);
1110
1179
  if (stmt.startIndex >= refNode.startIndex) break; // declaration-before-use
1111
- if (_declaresLocal(stmt, name, refNode)) return true;
1180
+ if (_declaresLocal(stmt, name, refNode)) {
1181
+ return owningFunctionStart(p);
1182
+ }
1112
1183
  }
1113
1184
  } else if (p.type === 'for_statement') {
1114
1185
  for (let i = 0; i < p.namedChildCount; i++) {
@@ -1118,38 +1189,55 @@ function findCallsInCode(code, parser, options = {}) {
1118
1189
  if (left) {
1119
1190
  for (let j = 0; j < left.namedChildCount; j++) {
1120
1191
  const id = left.namedChild(j);
1121
- if (id.type === 'identifier' && id.text === name) return true;
1192
+ if (id.type === 'identifier' && id.text === name) {
1193
+ return owningFunctionStart(p);
1194
+ }
1122
1195
  }
1123
1196
  }
1124
1197
  } else if (c.type === 'for_clause') {
1125
- if (_declaresLocal(c.childForFieldName('initializer'), name, refNode)) return true;
1198
+ if (_declaresLocal(c.childForFieldName('initializer'), name, refNode)) {
1199
+ return owningFunctionStart(p);
1200
+ }
1126
1201
  }
1127
1202
  }
1128
1203
  } else if (p.type === 'if_statement' || p.type === 'expression_switch_statement' ||
1129
1204
  p.type === 'type_switch_statement') {
1130
- if (_declaresLocal(p.childForFieldName('initializer'), name, refNode)) return true;
1205
+ if (_declaresLocal(p.childForFieldName('initializer'), name, refNode)) {
1206
+ return owningFunctionStart(p);
1207
+ }
1131
1208
  // if/switch initializers are plain named children in some
1132
1209
  // grammar versions; type switches bind `v := x.(type)`
1133
1210
  for (let i = 0; i < p.namedChildCount; i++) {
1134
1211
  const c = p.namedChild(i);
1135
- if (c.type === 'short_var_declaration' && _declaresLocal(c, name, refNode)) return true;
1212
+ if (c.type === 'short_var_declaration' &&
1213
+ _declaresLocal(c, name, refNode)) {
1214
+ return owningFunctionStart(p);
1215
+ }
1136
1216
  if (p.type === 'type_switch_statement' && c.type === 'expression_list' &&
1137
1217
  c.nextSibling?.type === ':=') {
1138
1218
  for (let j = 0; j < c.namedChildCount; j++) {
1139
1219
  const id = c.namedChild(j);
1140
- if (id.type === 'identifier' && id.text === name) return true;
1220
+ if (id.type === 'identifier' && id.text === name) {
1221
+ return owningFunctionStart(p);
1222
+ }
1141
1223
  }
1142
1224
  }
1143
1225
  }
1144
1226
  } else if (p.type === 'func_literal' || p.type === 'function_declaration' ||
1145
1227
  p.type === 'method_declaration') {
1146
- if (_paramListDeclares(p.childForFieldName('parameters'), name)) return true;
1228
+ if (_paramListDeclares(p.childForFieldName('parameters'), name)) {
1229
+ return p.startPosition.row + 1;
1230
+ }
1147
1231
  if (p.type === 'method_declaration' &&
1148
- _paramListDeclares(p.childForFieldName('receiver'), name)) return true;
1232
+ _paramListDeclares(p.childForFieldName('receiver'), name)) {
1233
+ return p.startPosition.row + 1;
1234
+ }
1149
1235
  }
1150
1236
  }
1151
- return false;
1237
+ return null;
1152
1238
  };
1239
+ const isShadowedByLocal = (refNode, name) =>
1240
+ lexicalBindingScopeStart(refNode, name) != null;
1153
1241
 
1154
1242
  traverseTree(tree.rootNode, (node) => {
1155
1243
  // Track function entry
@@ -1164,6 +1252,7 @@ function findCallsInCode(code, parser, options = {}) {
1164
1252
  scopeTypes.set(entry.startLine, typeMap);
1165
1253
  scopeTypeQualifiers.set(entry.startLine, typeQualifierMap);
1166
1254
  scopeContainerTypes.set(entry.startLine, containerMap || new Map());
1255
+ scopeIndexedSources.set(entry.startLine, new Map());
1167
1256
  if (funcParamNames.size > 0) {
1168
1257
  funcParamScopes.set(entry.startLine, funcParamNames);
1169
1258
  }
@@ -1186,21 +1275,7 @@ function findCallsInCode(code, parser, options = {}) {
1186
1275
  const valueVar = vars.length === 2 && vars[1].type === 'identifier' &&
1187
1276
  vars[1].text !== '_' ? vars[1].text : null;
1188
1277
  if (valueVar && right) {
1189
- let containerText = null;
1190
- if (right.type === 'selector_expression') {
1191
- const operand = right.childForFieldName('operand');
1192
- const fieldN = right.childForFieldName('field');
1193
- if (operand?.type === 'identifier' && fieldN &&
1194
- !isGuessedType(operand.text)) {
1195
- const rootType = getReceiverType(operand.text, operand);
1196
- if (rootType) {
1197
- containerText = getStructFields().get(rootType)?.get(fieldN.text) || null;
1198
- }
1199
- }
1200
- } else if (right.type === 'identifier') {
1201
- containerText = lookupContainerType(right.text);
1202
- }
1203
- const el = containerElementType(containerText);
1278
+ const el = containerElementType(containerTypeOf(right));
1204
1279
  if (el) {
1205
1280
  const scopeKey = functionStack[functionStack.length - 1].startLine;
1206
1281
  const typeMap = scopeTypes.get(scopeKey);
@@ -1237,6 +1312,7 @@ function findCallsInCode(code, parser, options = {}) {
1237
1312
  let typeName = null;
1238
1313
  let typeQualifier = null;
1239
1314
  let typeGuessed = false;
1315
+ let indexedSource = null;
1240
1316
  // &Type{...} or Type{...}
1241
1317
  if (val.type === 'composite_literal') {
1242
1318
  const typeNode = val.childForFieldName('type');
@@ -1301,6 +1377,34 @@ function findCallsInCode(code, parser, options = {}) {
1301
1377
  }
1302
1378
  }
1303
1379
  }
1380
+ } else if (val.type === 'index_expression') {
1381
+ // child, ok := holder.children[key] — map lookup
1382
+ // and slice/array indexing yield the container's
1383
+ // declared element type. In a comma-ok lookup the
1384
+ // first LHS receives the value; the existing
1385
+ // single-RHS pairing already selects that target.
1386
+ const container = val.childForFieldName('operand');
1387
+ const element = containerElementType(containerTypeOf(container));
1388
+ if (element) {
1389
+ typeName = element.type;
1390
+ typeQualifier = element.qualifier;
1391
+ }
1392
+ if (container?.type === 'selector_expression') {
1393
+ const root = container.childForFieldName('operand');
1394
+ const field = container.childForFieldName('field');
1395
+ if (root?.type === 'identifier' && field &&
1396
+ !isGuessedType(root.text)) {
1397
+ const rootType = getReceiverType(root.text, root);
1398
+ if (rootType) {
1399
+ indexedSource = {
1400
+ rootType,
1401
+ rootTypeQualifier:
1402
+ getReceiverTypeQualifier(root.text, root) || null,
1403
+ field: field.text,
1404
+ };
1405
+ }
1406
+ }
1407
+ }
1304
1408
  } else if (val.type === 'type_assertion_expression') {
1305
1409
  // d, ok := dialer.(proxy.ContextDialer) — the
1306
1410
  // asserted type IS d's static type (fix #298,
@@ -1333,6 +1437,9 @@ function findCallsInCode(code, parser, options = {}) {
1333
1437
  gset.delete(names[vi]); // compiler-true retype clears the guess
1334
1438
  }
1335
1439
  }
1440
+ const indexedSources = scopeIndexedSources.get(scopeKey);
1441
+ if (indexedSource) indexedSources?.set(names[vi], indexedSource);
1442
+ else indexedSources?.delete(names[vi]);
1336
1443
  }
1337
1444
  }
1338
1445
  }
@@ -1585,6 +1692,8 @@ function findCallsInCode(code, parser, options = {}) {
1585
1692
  ? getReceiverType(receiver, operandNode) : undefined;
1586
1693
  let receiverTypeQualifier = receiverType
1587
1694
  ? getReceiverTypeQualifier(receiver, operandNode) : undefined;
1695
+ const receiverIndexedSource = !isPkgCall && receiver && !receiverType
1696
+ ? getIndexedSource(receiver, operandNode) : undefined;
1588
1697
  // Composite-literal receiver (fix #298):
1589
1698
  // (&Kit{...}).Run(...) — compiler-true type, never guessed.
1590
1699
  if (!receiver && !receiverType) {
@@ -1597,7 +1706,7 @@ function findCallsInCode(code, parser, options = {}) {
1597
1706
  // fix #202: one-hop declared-field receivers — h.inner.Run().
1598
1707
  // receiverRoot/Field/RootType let findCallers hop to the
1599
1708
  // field's declared struct-field type cross-file.
1600
- let receiverRoot, receiverFieldName, receiverRootType,
1709
+ let receiverRoot, receiverRootNode, receiverFieldName, receiverRootType,
1601
1710
  receiverRootTypeQualifier, receiverRootTypeGuessed,
1602
1711
  receiverRootIsModule;
1603
1712
  if (!receiver && operandNode?.type === 'selector_expression') {
@@ -1605,6 +1714,7 @@ function findCallsInCode(code, parser, options = {}) {
1605
1714
  const fldNode = operandNode.childForFieldName('field');
1606
1715
  if (rootNode?.type === 'identifier' && fldNode) {
1607
1716
  receiverRoot = rootNode.text;
1717
+ receiverRootNode = rootNode;
1608
1718
  receiverFieldName = fldNode.text;
1609
1719
  if (importAliases.has(rootNode.text)) {
1610
1720
  // Package-owned value receiver:
@@ -1673,6 +1783,13 @@ function findCallsInCode(code, parser, options = {}) {
1673
1783
  }
1674
1784
  }
1675
1785
  const firstArg = getFirstStringArg(node);
1786
+ const receiverBindingNode = receiver ? operandNode : receiverRootNode;
1787
+ const receiverBindingName = receiver || receiverRoot;
1788
+ const methodEnclosingFunction = !isPkgCall &&
1789
+ receiverBindingNode && receiverBindingName
1790
+ ? getCurrentEnclosingFunction(
1791
+ receiverBindingNode, receiverBindingName)
1792
+ : enclosingFunction;
1676
1793
  calls.push({
1677
1794
  name: fieldNode.text,
1678
1795
  // Name-node line convention (#201/RUST-2, fix #223):
@@ -1690,6 +1807,14 @@ function findCallsInCode(code, parser, options = {}) {
1690
1807
  ...(receiverType && { receiverType }),
1691
1808
  ...(receiverTypeQualifier && { receiverTypeQualifier }),
1692
1809
  ...(receiverType && isGuessedType(receiver) && { receiverTypeGuessed: true }),
1810
+ ...(receiverIndexedSource && {
1811
+ receiverIndexRootType: receiverIndexedSource.rootType,
1812
+ receiverIndexField: receiverIndexedSource.field,
1813
+ ...(receiverIndexedSource.rootTypeQualifier && {
1814
+ receiverIndexRootTypeQualifier:
1815
+ receiverIndexedSource.rootTypeQualifier,
1816
+ }),
1817
+ }),
1693
1818
  ...(receiverFieldName && { receiverRoot, receiverField: receiverFieldName }),
1694
1819
  ...(receiverRootIsModule && { receiverRootIsModule: true }),
1695
1820
  ...(receiverFieldName && receiverRootType && { receiverRootType }),
@@ -1716,7 +1841,7 @@ function findCallsInCode(code, parser, options = {}) {
1716
1841
  assignedTupleTargets: assigned.assignedTupleTargets,
1717
1842
  }),
1718
1843
  ...(assigned?.assignedTupleRest && { assignedTupleRest: assigned.assignedTupleRest }),
1719
- enclosingFunction,
1844
+ enclosingFunction: methodEnclosingFunction,
1720
1845
  uncertain,
1721
1846
  ...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp })
1722
1847
  });
@@ -1796,7 +1921,9 @@ function findCallsInCode(code, parser, options = {}) {
1796
1921
  receiverTypeQualifier = lit.receiverTypeQualifier;
1797
1922
  }
1798
1923
  }
1799
- const enclosingFunction = getCurrentEnclosingFunction();
1924
+ const enclosingFunction = receiver
1925
+ ? getCurrentEnclosingFunction(operandNode, receiver)
1926
+ : getCurrentEnclosingFunction();
1800
1927
  calls.push({
1801
1928
  name: fieldNode.text,
1802
1929
  line: fieldNode.startPosition.row + 1,
@@ -2070,6 +2197,7 @@ function findCallsInCode(code, parser, options = {}) {
2070
2197
  closureScopes.delete(leaving.startLine);
2071
2198
  scopeTypes.delete(leaving.startLine);
2072
2199
  scopeGuesses.delete(leaving.startLine);
2200
+ scopeIndexedSources.delete(leaving.startLine);
2073
2201
  }
2074
2202
  }
2075
2203
  }