ucn 4.1.3 → 4.2.1

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
@@ -31,6 +31,14 @@ function extractReturnType(node) {
31
31
  return null;
32
32
  }
33
33
 
34
+ /** Compiler-declared result of a returned function type, if any. */
35
+ function extractReturnedFunctionResult(node) {
36
+ const resultNode = node.childForFieldName('result');
37
+ if (resultNode?.type !== 'function_type') return null;
38
+ const innerResult = resultNode.childForFieldName('result');
39
+ return innerResult?.text.trim() || null;
40
+ }
41
+
34
42
  /**
35
43
  * Extract Go parameters
36
44
  */
@@ -70,6 +78,46 @@ function extractReceiver(receiverNode) {
70
78
  * Returns true if node was matched, false otherwise
71
79
  */
72
80
  function _processFunction(node, functions, processedRanges, lines) {
81
+ // Package-level function-valued variables are callable symbols with a
82
+ // compiler-declared signature (`var DefaultLogger func(next
83
+ // http.Handler) http.Handler`). Indexing them as functions lets return
84
+ // flow type the result and prevents downstream interface calls from
85
+ // falling into global same-name guesses. Local function variables remain
86
+ // lexical values and are handled by call-site shadowing instead.
87
+ if (node.type === 'var_spec') {
88
+ const container = node.parent?.type === 'var_spec_list' ? node.parent?.parent : node.parent;
89
+ const isPackageLevel = container?.type === 'var_declaration' &&
90
+ container.parent?.type === 'source_file';
91
+ const typeNode = node.childForFieldName('type');
92
+ if (!isPackageLevel || typeNode?.type !== 'function_type') return false;
93
+ const rangeKey = `${node.startIndex}-${node.endIndex}`;
94
+ if (processedRanges.has(rangeKey)) return false;
95
+ processedRanges.add(rangeKey);
96
+ const paramsNode = typeNode.childForFieldName('parameters');
97
+ const resultNode = typeNode.childForFieldName('result');
98
+ const names = [];
99
+ for (let i = 0; i < node.namedChildCount; i++) {
100
+ const child = node.namedChild(i);
101
+ if (child.type === 'identifier') names.push(child);
102
+ }
103
+ const { startLine, endLine, indent } = nodeToLocation(node, lines);
104
+ for (const nameNode of names) {
105
+ const isExported = /^[A-Z]/.test(nameNode.text);
106
+ functions.push({
107
+ name: nameNode.text,
108
+ params: extractGoParams(paramsNode),
109
+ paramsStructured: parseStructuredParams(paramsNode, 'go'),
110
+ startLine,
111
+ endLine,
112
+ indent,
113
+ modifiers: isExported ? ['export'] : [],
114
+ isFunctionVariable: true,
115
+ ...(resultNode?.text.trim() && { returnType: resultNode.text.trim() }),
116
+ });
117
+ }
118
+ return true;
119
+ }
120
+
73
121
  if (node.type === 'function_declaration') {
74
122
  const rangeKey = `${node.startIndex}-${node.endIndex}`;
75
123
  if (processedRanges.has(rangeKey)) return false;
@@ -81,6 +129,7 @@ function _processFunction(node, functions, processedRanges, lines) {
81
129
  if (nameNode) {
82
130
  const { startLine, endLine, indent } = nodeToLocation(node, lines);
83
131
  const returnType = extractReturnType(node);
132
+ const returnedFunctionResult = extractReturnedFunctionResult(node);
84
133
  const docstring = extractGoDocstring(lines, startLine);
85
134
  const typeParams = extractTypeParams(node);
86
135
  const isExported = /^[A-Z]/.test(nameNode.text);
@@ -94,6 +143,7 @@ function _processFunction(node, functions, processedRanges, lines) {
94
143
  indent,
95
144
  modifiers: isExported ? ['export'] : [],
96
145
  ...(returnType && { returnType }),
146
+ ...(returnedFunctionResult && { returnedFunctionResult }),
97
147
  ...(docstring && { docstring }),
98
148
  ...(typeParams && { generics: typeParams })
99
149
  });
@@ -114,6 +164,7 @@ function _processFunction(node, functions, processedRanges, lines) {
114
164
  const { startLine, endLine, indent } = nodeToLocation(node, lines);
115
165
  const receiver = extractReceiver(receiverNode);
116
166
  const returnType = extractReturnType(node);
167
+ const returnedFunctionResult = extractReturnedFunctionResult(node);
117
168
  const docstring = extractGoDocstring(lines, startLine);
118
169
  const isExported = /^[A-Z]/.test(nameNode.text);
119
170
 
@@ -128,6 +179,7 @@ function _processFunction(node, functions, processedRanges, lines) {
128
179
  receiver,
129
180
  modifiers: isExported ? ['export'] : [],
130
181
  ...(returnType && { returnType }),
182
+ ...(returnedFunctionResult && { returnedFunctionResult }),
131
183
  ...(docstring && { docstring })
132
184
  });
133
185
  }
@@ -567,6 +619,7 @@ function parse(code, parser) {
567
619
  functions,
568
620
  classes,
569
621
  stateObjects,
622
+ ...(tree.rootNode.hasError && { parseRecovery: true }),
570
623
  imports: [],
571
624
  exports: []
572
625
  };
@@ -656,7 +709,7 @@ function findCallsInCode(code, parser, options = {}) {
656
709
  if (!argsNode) return null;
657
710
  for (let i = 0; i < argsNode.namedChildCount; i++) {
658
711
  const arg = argsNode.namedChild(i);
659
- if (arg.type === 'comment') continue;
712
+ if (arg.type.endsWith('comment')) continue;
660
713
  // fmt.Sprintf interpolation
661
714
  if (arg.type === 'call_expression') {
662
715
  const inner = arg.childForFieldName('function');
@@ -678,6 +731,8 @@ function findCallsInCode(code, parser, options = {}) {
678
731
  const closureScopes = new Map();
679
732
  // Track variable -> type mappings per function scope (scopeStartLine -> Map<varName, typeName>)
680
733
  const scopeTypes = new Map();
734
+ // Package qualifier paired with an inferred/declared receiver type.
735
+ const scopeTypeQualifiers = new Map();
681
736
  // Names whose scope type is a New*-prefix GUESS (fix #266) — per scope
682
737
  const scopeGuesses = new Map();
683
738
  // Track function-typed parameter names per scope (scopeStartLine -> Set<name>)
@@ -717,11 +772,31 @@ function findCallsInCode(code, parser, options = {}) {
717
772
  return null;
718
773
  };
719
774
 
775
+ // Preserve the package qualifier separately from the bare type name.
776
+ // `http.Handler` and a project-local `Handler` are different identities;
777
+ // dropping `http.` lets an unrelated same-named project type steal method
778
+ // dispatch. Pointer wrappers do not change that provenance.
779
+ const extractTypeQualifier = (typeNode) => {
780
+ if (!typeNode) return null;
781
+ if (typeNode.type === 'pointer_type') {
782
+ for (let i = 0; i < typeNode.namedChildCount; i++) {
783
+ const q = extractTypeQualifier(typeNode.namedChild(i));
784
+ if (q) return q;
785
+ }
786
+ }
787
+ if (typeNode.type === 'qualified_type') {
788
+ const pkg = typeNode.childForFieldName('package') || typeNode.namedChild(0);
789
+ return pkg?.text || null;
790
+ }
791
+ return null;
792
+ };
793
+
720
794
  // Build type map from function/method parameters and receiver.
721
795
  // Also returns funcParamNames: parameter names with function types (func(...) ...)
722
796
  // so calls to them can be skipped (they're local parameter calls, not global function calls).
723
797
  const buildScopeTypeMap = (node) => {
724
798
  const typeMap = new Map();
799
+ const typeQualifierMap = new Map();
725
800
  const funcParamNames = new Set();
726
801
 
727
802
  // Method receiver: func (f *Framework) Method()
@@ -736,6 +811,8 @@ function findCallsInCode(code, parser, options = {}) {
736
811
  const typeName = extractTypeName(typeNode);
737
812
  if (nameNode && typeName) {
738
813
  typeMap.set(nameNode.text, typeName);
814
+ const qualifier = extractTypeQualifier(typeNode);
815
+ if (qualifier) typeQualifierMap.set(nameNode.text, qualifier);
739
816
  }
740
817
  }
741
818
  }
@@ -764,14 +841,18 @@ function findCallsInCode(code, parser, options = {}) {
764
841
  } else {
765
842
  const typeName = extractTypeName(typeNode);
766
843
  if (typeName) {
767
- for (const nn of nameNodes) typeMap.set(nn.text, typeName);
844
+ const qualifier = extractTypeQualifier(typeNode);
845
+ for (const nn of nameNodes) {
846
+ typeMap.set(nn.text, typeName);
847
+ if (qualifier) typeQualifierMap.set(nn.text, qualifier);
848
+ }
768
849
  }
769
850
  }
770
851
  }
771
852
  }
772
853
  }
773
854
 
774
- return { typeMap, funcParamNames };
855
+ return { typeMap, typeQualifierMap, funcParamNames };
775
856
  };
776
857
 
777
858
  // Helper to extract function name from a function node
@@ -824,6 +905,13 @@ function findCallsInCode(code, parser, options = {}) {
824
905
  }
825
906
  return undefined;
826
907
  };
908
+ const getReceiverTypeQualifier = (varName) => {
909
+ for (let i = functionStack.length - 1; i >= 0; i--) {
910
+ const qualifiers = scopeTypeQualifiers.get(functionStack[i].startLine);
911
+ if (qualifiers?.has(varName)) return qualifiers.get(varName);
912
+ }
913
+ return undefined;
914
+ };
827
915
  // Is the FIRST scope-chain hit for this variable a New*-prefix GUESS
828
916
  // (fix #266, viper-measured)? `registry := NewCodecRegistry()` types
829
917
  // registry as 'CodecRegistry' by NAME CONVENTION — the actual return
@@ -947,8 +1035,9 @@ function findCallsInCode(code, parser, options = {}) {
947
1035
  endLine: node.endPosition.row + 1
948
1036
  };
949
1037
  functionStack.push(entry);
950
- const { typeMap, funcParamNames } = buildScopeTypeMap(node);
1038
+ const { typeMap, typeQualifierMap, funcParamNames } = buildScopeTypeMap(node);
951
1039
  scopeTypes.set(entry.startLine, typeMap);
1040
+ scopeTypeQualifiers.set(entry.startLine, typeQualifierMap);
952
1041
  if (funcParamNames.size > 0) {
953
1042
  funcParamScopes.set(entry.startLine, funcParamNames);
954
1043
  }
@@ -974,15 +1063,20 @@ function findCallsInCode(code, parser, options = {}) {
974
1063
  for (let vi = 0; vi < Math.min(names.length, values.length); vi++) {
975
1064
  const val = values[vi];
976
1065
  let typeName = null;
1066
+ let typeQualifier = null;
977
1067
  let typeGuessed = false;
978
1068
  // &Type{...} or Type{...}
979
1069
  if (val.type === 'composite_literal') {
980
- typeName = extractTypeName(val.childForFieldName('type'));
1070
+ const typeNode = val.childForFieldName('type');
1071
+ typeName = extractTypeName(typeNode);
1072
+ typeQualifier = extractTypeQualifier(typeNode);
981
1073
  } else if (val.type === 'unary_expression' && val.childCount > 0) {
982
1074
  for (let ci = 0; ci < val.namedChildCount; ci++) {
983
1075
  const ch = val.namedChild(ci);
984
1076
  if (ch.type === 'composite_literal') {
985
- typeName = extractTypeName(ch.childForFieldName('type'));
1077
+ const typeNode = ch.childForFieldName('type');
1078
+ typeName = extractTypeName(typeNode);
1079
+ typeQualifier = extractTypeQualifier(typeNode);
986
1080
  break;
987
1081
  }
988
1082
  }
@@ -1020,12 +1114,20 @@ function findCallsInCode(code, parser, options = {}) {
1020
1114
  : argNode.type === 'selector_expression'
1021
1115
  ? argNode.childForFieldName('field')?.text || null
1022
1116
  : extractTypeName(argNode);
1117
+ if (argNode.type === 'selector_expression') {
1118
+ typeQualifier = argNode.childForFieldName('operand')?.text || null;
1119
+ } else {
1120
+ typeQualifier = extractTypeQualifier(argNode);
1121
+ }
1023
1122
  }
1024
1123
  }
1025
1124
  }
1026
1125
  }
1027
1126
  if (typeName) {
1028
1127
  typeMap.set(names[vi], typeName);
1128
+ const qualifiers = scopeTypeQualifiers.get(scopeKey);
1129
+ if (typeQualifier) qualifiers?.set(names[vi], typeQualifier);
1130
+ else qualifiers?.delete(names[vi]);
1029
1131
  let gset = scopeGuesses.get(scopeKey);
1030
1132
  if (typeGuessed) {
1031
1133
  if (!gset) { gset = new Set(); scopeGuesses.set(scopeKey, gset); }
@@ -1052,10 +1154,14 @@ function findCallsInCode(code, parser, options = {}) {
1052
1154
  if (spec.type !== 'var_spec') return;
1053
1155
  const typeName = extractTypeName(spec.childForFieldName('type'));
1054
1156
  if (!typeName) return;
1157
+ const qualifier = extractTypeQualifier(spec.childForFieldName('type'));
1055
1158
  for (let j = 0; j < spec.namedChildCount; j++) {
1056
1159
  const id = spec.namedChild(j);
1057
1160
  if (id.type === 'identifier') {
1058
1161
  varTypeMap.set(id.text, typeName);
1162
+ const qualifiers = scopeTypeQualifiers.get(scopeKey);
1163
+ if (qualifier) qualifiers?.set(id.text, qualifier);
1164
+ else qualifiers?.delete(id.text);
1059
1165
  scopeGuesses.get(scopeKey)?.delete(id.text); // declared type clears any guess
1060
1166
  }
1061
1167
  }
@@ -1134,7 +1240,7 @@ function findCallsInCode(code, parser, options = {}) {
1134
1240
  let argSpread = false;
1135
1241
  if (argsNode) {
1136
1242
  for (let i = 0; i < argsNode.namedChildCount; i++) {
1137
- if (argsNode.namedChild(i).type === 'comment') continue;
1243
+ if (argsNode.namedChild(i).type.endsWith('comment')) continue;
1138
1244
  argCount++;
1139
1245
  }
1140
1246
  for (let i = 0; i < argsNode.childCount; i++) {
@@ -1183,20 +1289,32 @@ function findCallsInCode(code, parser, options = {}) {
1183
1289
  // If receiver is a known import alias, this is a package call, not a method call
1184
1290
  const isPkgCall = receiver && importAliases.has(receiver);
1185
1291
  const receiverType = (!isPkgCall && receiver) ? getReceiverType(receiver) : undefined;
1292
+ const receiverTypeQualifier = receiverType
1293
+ ? getReceiverTypeQualifier(receiver) : undefined;
1186
1294
  // fix #202: one-hop declared-field receivers — h.inner.Run().
1187
1295
  // receiverRoot/Field/RootType let findCallers hop to the
1188
1296
  // field's declared struct-field type cross-file.
1189
- let receiverRoot, receiverFieldName, receiverRootType, receiverRootTypeGuessed;
1297
+ let receiverRoot, receiverFieldName, receiverRootType,
1298
+ receiverRootTypeQualifier, receiverRootTypeGuessed,
1299
+ receiverRootIsModule;
1190
1300
  if (!receiver && operandNode?.type === 'selector_expression') {
1191
1301
  const rootNode = operandNode.childForFieldName('operand');
1192
1302
  const fldNode = operandNode.childForFieldName('field');
1193
- if (rootNode?.type === 'identifier' && fldNode &&
1194
- !importAliases.has(rootNode.text)) {
1303
+ if (rootNode?.type === 'identifier' && fldNode) {
1195
1304
  receiverRoot = rootNode.text;
1196
1305
  receiverFieldName = fldNode.text;
1197
- receiverRootType = getReceiverType(rootNode.text);
1198
- if (receiverRootType && isGuessedType(rootNode.text)) {
1199
- receiverRootTypeGuessed = true;
1306
+ if (importAliases.has(rootNode.text)) {
1307
+ // Package-owned value receiver:
1308
+ // io.Discard.Write(...). The package qualifier
1309
+ // is identity evidence even though the value's
1310
+ // concrete type lives outside the project.
1311
+ receiverRootIsModule = true;
1312
+ } else {
1313
+ receiverRootType = getReceiverType(rootNode.text);
1314
+ receiverRootTypeQualifier = getReceiverTypeQualifier(rootNode.text);
1315
+ if (receiverRootType && isGuessedType(rootNode.text)) {
1316
+ receiverRootTypeGuessed = true;
1317
+ }
1200
1318
  }
1201
1319
  }
1202
1320
  }
@@ -1243,9 +1361,12 @@ function findCallsInCode(code, parser, options = {}) {
1243
1361
  isMethod: !isPkgCall,
1244
1362
  receiver,
1245
1363
  ...(receiverType && { receiverType }),
1364
+ ...(receiverTypeQualifier && { receiverTypeQualifier }),
1246
1365
  ...(receiverType && isGuessedType(receiver) && { receiverTypeGuessed: true }),
1247
1366
  ...(receiverFieldName && { receiverRoot, receiverField: receiverFieldName }),
1367
+ ...(receiverRootIsModule && { receiverRootIsModule: true }),
1248
1368
  ...(receiverFieldName && receiverRootType && { receiverRootType }),
1369
+ ...(receiverFieldName && receiverRootTypeQualifier && { receiverRootTypeQualifier }),
1249
1370
  ...(receiverFieldName && receiverRootType && receiverRootTypeGuessed && { receiverRootTypeGuessed: true }),
1250
1371
  ...(receiverCall && { receiverCall }),
1251
1372
  ...(receiverCallIsMethod && { receiverCallIsMethod: true }),
@@ -1323,6 +1444,8 @@ function findCallsInCode(code, parser, options = {}) {
1323
1444
  if (fieldNode && operandNode) {
1324
1445
  const receiver = operandNode.type === 'identifier' ? operandNode.text : undefined;
1325
1446
  const receiverType = receiver ? getReceiverType(receiver) : undefined;
1447
+ const receiverTypeQualifier = receiverType
1448
+ ? getReceiverTypeQualifier(receiver) : undefined;
1326
1449
  const enclosingFunction = getCurrentEnclosingFunction();
1327
1450
  calls.push({
1328
1451
  name: fieldNode.text,
@@ -1330,6 +1453,7 @@ function findCallsInCode(code, parser, options = {}) {
1330
1453
  isMethod: true,
1331
1454
  receiver,
1332
1455
  ...(receiverType && { receiverType }),
1456
+ ...(receiverTypeQualifier && { receiverTypeQualifier }),
1333
1457
  ...(receiverType && isGuessedType(receiver) && { receiverTypeGuessed: true }),
1334
1458
  enclosingFunction,
1335
1459
  isPotentialCallback: true,
@@ -1379,6 +1503,8 @@ function findCallsInCode(code, parser, options = {}) {
1379
1503
  if (fieldNode && operandNode) {
1380
1504
  const receiver = operandNode.type === 'identifier' ? operandNode.text : undefined;
1381
1505
  const receiverType = receiver ? getReceiverType(receiver) : undefined;
1506
+ const receiverTypeQualifier = receiverType
1507
+ ? getReceiverTypeQualifier(receiver) : undefined;
1382
1508
  const enclosingFunction = getCurrentEnclosingFunction();
1383
1509
  calls.push({
1384
1510
  name: fieldNode.text,
@@ -1386,6 +1512,7 @@ function findCallsInCode(code, parser, options = {}) {
1386
1512
  isMethod: true,
1387
1513
  receiver,
1388
1514
  ...(receiverType && { receiverType }),
1515
+ ...(receiverTypeQualifier && { receiverTypeQualifier }),
1389
1516
  ...(receiverType && isGuessedType(receiver) && { receiverTypeGuessed: true }),
1390
1517
  enclosingFunction,
1391
1518
  isPotentialCallback: true,
@@ -1474,6 +1601,8 @@ function findCallsInCode(code, parser, options = {}) {
1474
1601
  if (fieldNode && operandNode) {
1475
1602
  const receiver = operandNode.type === 'identifier' ? operandNode.text : undefined;
1476
1603
  const receiverType = receiver ? getReceiverType(receiver) : undefined;
1604
+ const receiverTypeQualifier = receiverType
1605
+ ? getReceiverTypeQualifier(receiver) : undefined;
1477
1606
  const enclosingFunction = getCurrentEnclosingFunction();
1478
1607
  calls.push({
1479
1608
  name: fieldNode.text,
@@ -1481,6 +1610,7 @@ function findCallsInCode(code, parser, options = {}) {
1481
1610
  isMethod: true,
1482
1611
  receiver,
1483
1612
  ...(receiverType && { receiverType }),
1613
+ ...(receiverTypeQualifier && { receiverTypeQualifier }),
1484
1614
  ...(receiverType && isGuessedType(receiver) && { receiverTypeGuessed: true }),
1485
1615
  enclosingFunction,
1486
1616
  isPotentialCallback: true,
package/languages/html.js CHANGED
@@ -224,6 +224,9 @@ function extractEventHandlerCalls(htmlContent, htmlParser) {
224
224
  // ── Exported language module interface ──────────────────────────────────────
225
225
 
226
226
  function parse(code, parser) {
227
+ const { safeParse, getParseOptions } = require('./index');
228
+ const htmlTree = safeParse(parser, code, undefined, getParseOptions(code.length));
229
+ const htmlRecovery = !!htmlTree.rootNode.hasError;
227
230
  const result = extractJS(code, parser);
228
231
  if (!result) {
229
232
  return {
@@ -232,6 +235,7 @@ function parse(code, parser) {
232
235
  functions: [],
233
236
  classes: [],
234
237
  stateObjects: [],
238
+ ...(htmlRecovery && { parseRecovery: true }),
235
239
  imports: [],
236
240
  exports: []
237
241
  };
@@ -240,6 +244,7 @@ function parse(code, parser) {
240
244
  const jsResult = result.jsModule.parse(result.virtualJS, result.jsParser);
241
245
  jsResult.language = 'html';
242
246
  jsResult.totalLines = code.split('\n').length;
247
+ if (htmlRecovery) jsResult.parseRecovery = true;
243
248
  return jsResult;
244
249
  }
245
250
 
@@ -50,6 +50,9 @@ const STRUCTURAL_TRAITS = {
50
50
  // assignment rebinds functions onto objects (obj.print = print), so the
51
51
  // #218b gate routes such calls visible instead of excluding (fix #220).
52
52
  methodCallReachesFunctions: true,
53
+ // Whether `Type(...)` constructs a class without a `new` token. Python
54
+ // classes are ordinary callable objects; JS/TS classes require `new`.
55
+ classesCallableWithoutNew: false,
53
56
  };
54
57
  const NOMINAL_TRAITS = {
55
58
  typeSystem: 'nominal',
@@ -113,6 +116,7 @@ const NOMINAL_TRAITS = {
113
116
  // Go method values (obj.Method) and Java `::` references DO denote
114
117
  // methods — false. Per-language override, not preset-wide.
115
118
  memberAccessNeverMethod: false,
119
+ classesCallableWithoutNew: false,
116
120
  };
117
121
 
118
122
  // Language configurations
@@ -170,6 +174,7 @@ const LANGUAGES = {
170
174
  // and query code treats such receivers as module receivers. JS
171
175
  // from-imports bind values only (`import * as ns` is parser-marked).
172
176
  submoduleImports: true,
177
+ classesCallableWithoutNew: true,
173
178
  testFileCandidates: (base, ext) => [`test_${base}.py`, `${base}_test.py`],
174
179
  testDirs: ['tests'],
175
180
  },
package/languages/java.js CHANGED
@@ -295,6 +295,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
295
295
  isConstructor: true,
296
296
  ...(docstring && { docstring }),
297
297
  ...(annotations.length > 0 && { annotations }),
298
+ ...(annotationsWithArgs.length > 0 && { annotationsWithArgs }),
298
299
  ...(nameLine !== startLine && { nameLine })
299
300
  });
300
301
  }
@@ -423,7 +424,8 @@ function _processClass(node, classes, processedRanges, lines, code) {
423
424
  members: extractEnumConstants(node, lines),
424
425
  modifiers,
425
426
  ...(docstring && { docstring }),
426
- ...(annotations.length > 0 && { annotations })
427
+ ...(annotations.length > 0 && { annotations }),
428
+ ...(annotationsWithArgs.length > 0 && { annotationsWithArgs })
427
429
  });
428
430
  }
429
431
  return true;
@@ -479,6 +481,7 @@ function _processClass(node, classes, processedRanges, lines, code) {
479
481
  ...(docstring && { docstring }),
480
482
  ...(generics && { generics }),
481
483
  ...(annotations.length > 0 && { annotations }),
484
+ ...(annotationsWithArgs.length > 0 && { annotationsWithArgs }),
482
485
  ...(implementsInfo.length > 0 && { implements: implementsInfo })
483
486
  });
484
487
  }
@@ -843,6 +846,7 @@ function parse(code, parser) {
843
846
  functions,
844
847
  classes,
845
848
  stateObjects,
849
+ ...(tree.rootNode.hasError && { parseRecovery: true }),
846
850
  imports: [],
847
851
  exports: []
848
852
  };
@@ -869,7 +873,7 @@ function findCallsInCode(code, parser) {
869
873
  if (!argsNode) return null;
870
874
  for (let i = 0; i < argsNode.namedChildCount; i++) {
871
875
  const arg = argsNode.namedChild(i);
872
- if (arg.type === 'comment') continue;
876
+ if (arg.type.endsWith('comment')) continue;
873
877
  return _extractStringArg(arg);
874
878
  }
875
879
  return null;
@@ -979,6 +983,7 @@ function findCallsInCode(code, parser) {
979
983
  // as an implicit-this field when NO local of that name is declared —
980
984
  // mistyping a shadowed local could wrongly exclude a true caller.
981
985
  const scopeDeclared = new Map();
986
+ const scopedTypeRestores = new Map();
982
987
  const collectDeclaredNames = (fnNode) => {
983
988
  const declared = new Set();
984
989
  const walk = (n) => {
@@ -1064,6 +1069,27 @@ function findCallsInCode(code, parser) {
1064
1069
  const tn = arg.childForFieldName('type');
1065
1070
  return tn ? `cast:${bareTypeName(tn.text)}` : 'expr';
1066
1071
  }
1072
+ case 'class_literal': {
1073
+ const tn = arg.namedChild(0);
1074
+ return tn ? `class:${bareTypeName(tn.text)}` : 'class:Object';
1075
+ }
1076
+ case 'identifier': {
1077
+ const typeName = getReceiverType(arg.text);
1078
+ return typeName ? `type:${bareTypeName(typeName)}` : 'expr';
1079
+ }
1080
+ case 'method_invocation': {
1081
+ // Preserve compiler-visible producer ownership for nested
1082
+ // static factories: build(CodeBlock.of(...)) can select a
1083
+ // CodeBlock overload without guessing from the method name.
1084
+ const ownerNode = arg.childForFieldName('object');
1085
+ const methodNode = arg.childForFieldName('name');
1086
+ if (ownerNode?.type === 'identifier' && methodNode) {
1087
+ const ownerType = getReceiverType(ownerNode.text) ||
1088
+ (/^[A-Z]/.test(ownerNode.text) ? ownerNode.text : null);
1089
+ if (ownerType) return `call:${bareTypeName(ownerType)}:${methodNode.text}`;
1090
+ }
1091
+ return 'expr';
1092
+ }
1067
1093
  case 'lambda_expression':
1068
1094
  case 'method_reference': return 'lambda';
1069
1095
  case 'unary_expression':
@@ -1078,7 +1104,7 @@ function findCallsInCode(code, parser) {
1078
1104
  const kinds = [];
1079
1105
  for (let i = 0; i < argsNode.namedChildCount; i++) {
1080
1106
  const arg = argsNode.namedChild(i);
1081
- if (arg.type === 'comment') continue;
1107
+ if (arg.type.endsWith('comment')) continue;
1082
1108
  kinds.push(argKindOf(arg));
1083
1109
  }
1084
1110
  return { argCount: kinds.length, argKinds: kinds.some(k => k !== 'expr') ? kinds : null };
@@ -1097,6 +1123,26 @@ function findCallsInCode(code, parser) {
1097
1123
  scopeDeclared.set(entry.startLine, collectDeclaredNames(node));
1098
1124
  }
1099
1125
 
1126
+ // Enhanced-for variables are compiler-typed locals whose scope is
1127
+ // exactly the loop body. Record the type before visiting the body and
1128
+ // restore any shadowed outer binding on leave.
1129
+ if (node.type === 'enhanced_for_statement' && functionStack.length > 0) {
1130
+ const typeNode = node.childForFieldName('type');
1131
+ const nameNode = node.childForFieldName('name');
1132
+ const typeName = extractTypeName(typeNode);
1133
+ const scopeKey = functionStack[functionStack.length - 1].startLine;
1134
+ const typeMap = scopeTypes.get(scopeKey);
1135
+ if (nameNode && typeName && typeMap) {
1136
+ scopedTypeRestores.set(node.id, {
1137
+ typeMap,
1138
+ name: nameNode.text,
1139
+ had: typeMap.has(nameNode.text),
1140
+ previous: typeMap.get(nameNode.text),
1141
+ });
1142
+ typeMap.set(nameNode.text, typeName);
1143
+ }
1144
+ }
1145
+
1100
1146
  // Handle method invocations: foo(), obj.foo(), this.foo()
1101
1147
  if (node.type === 'method_invocation') {
1102
1148
  const nameNode = node.childForFieldName('name');
@@ -1121,7 +1167,12 @@ function findCallsInCode(code, parser) {
1121
1167
  receiverFieldName = fldNode.text;
1122
1168
  receiverRootType = findEnclosingClassName(node);
1123
1169
  } else if (rootNode.type === 'identifier') {
1124
- const rootType = getReceiverType(rootNode.text);
1170
+ // `TypeName.CONSTANT.method()` is a one-hop
1171
+ // static-field receiver. The capitalized root
1172
+ // is type identity, then the field declaration
1173
+ // supplies the value's static type.
1174
+ const rootType = getReceiverType(rootNode.text) ||
1175
+ (/^[A-Z]/.test(rootNode.text) ? rootNode.text : undefined);
1125
1176
  if (rootType) {
1126
1177
  receiverRoot = rootNode.text;
1127
1178
  receiverFieldName = fldNode.text;
@@ -1363,6 +1414,12 @@ function findCallsInCode(code, parser) {
1363
1414
  return true;
1364
1415
  }, {
1365
1416
  onLeave: (node) => {
1417
+ const restore = scopedTypeRestores.get(node.id);
1418
+ if (restore) {
1419
+ if (restore.had) restore.typeMap.set(restore.name, restore.previous);
1420
+ else restore.typeMap.delete(restore.name);
1421
+ scopedTypeRestores.delete(node.id);
1422
+ }
1366
1423
  if (isFunctionNode(node)) {
1367
1424
  const leaving = functionStack.pop();
1368
1425
  if (leaving) {