ucn 5.4.2 → 5.5.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.
Files changed (85) hide show
  1. package/.claude/skills/ucn/SKILL.md +58 -236
  2. package/.claude/skills/ucn/references/commands.md +144 -68
  3. package/.claude/skills/ucn/references/resolution-rules.md +171 -0
  4. package/.claude/skills/ucn/references/trust-contract.md +27 -33
  5. package/README.md +27 -16
  6. package/cli/index.js +45 -14
  7. package/core/accessors.js +118 -3
  8. package/core/account.js +62 -11
  9. package/core/analysis.js +517 -266
  10. package/core/ast-analysis.js +76 -117
  11. package/core/async-producers.js +1729 -0
  12. package/core/bridge.js +1877 -268
  13. package/core/brief.js +6 -0
  14. package/core/build-worker.js +78 -14
  15. package/core/cache.js +529 -31
  16. package/core/callers.js +12228 -921
  17. package/core/check.js +30 -38
  18. package/core/class-identity.js +1028 -0
  19. package/core/command-contracts.js +6 -6
  20. package/core/configuration-items.js +86 -0
  21. package/core/contract-membership.js +2262 -0
  22. package/core/cpp-scope.js +532 -0
  23. package/core/deadcode.js +171 -49
  24. package/core/decorator-binding.js +260 -0
  25. package/core/discovery.js +281 -164
  26. package/core/errors.js +52 -0
  27. package/core/execute.js +21 -9
  28. package/core/external-macros.js +326 -0
  29. package/core/graph-build.js +277 -8
  30. package/core/graph.js +35 -2
  31. package/core/ignore-rules.js +472 -0
  32. package/core/imports.js +504 -42
  33. package/core/index-ir.js +94 -11
  34. package/core/inprocess-clients.js +636 -0
  35. package/core/ir.js +87 -8
  36. package/core/macro-expansion.js +1416 -0
  37. package/core/output/analysis.js +18 -2
  38. package/core/output/brief.js +3 -0
  39. package/core/output/doctor.js +4 -0
  40. package/core/output/endpoints.js +15 -6
  41. package/core/output/find.js +7 -2
  42. package/core/output/refactoring.js +121 -14
  43. package/core/output/reporting.js +31 -3
  44. package/core/output/shared.js +14 -0
  45. package/core/output-budget.js +305 -122
  46. package/core/parallel-build.js +124 -47
  47. package/core/parser.js +3 -2
  48. package/core/project.js +472 -66
  49. package/core/provenance-facts.js +156 -12
  50. package/core/provenance-overload.js +30 -3
  51. package/core/provenance.js +104 -30
  52. package/core/pytest-fixtures.js +434 -0
  53. package/core/python-fixture-flow.js +17 -3
  54. package/core/python-modules.js +173 -0
  55. package/core/reflection.js +877 -0
  56. package/core/registry.js +1 -1
  57. package/core/reporting.js +234 -52
  58. package/core/request-helpers.js +561 -0
  59. package/core/route-graph.js +1922 -0
  60. package/core/route-lists.js +2414 -0
  61. package/core/rust-macro-expansion.js +3026 -0
  62. package/core/rust-modules.js +203 -0
  63. package/core/search.js +121 -29
  64. package/core/shared.js +19 -1
  65. package/core/test-name-bindings.js +256 -0
  66. package/core/type-denotation.js +1185 -0
  67. package/core/type-references.js +3229 -0
  68. package/core/verify.js +3360 -672
  69. package/languages/adapter.js +31 -5
  70. package/languages/c-family.js +4061 -212
  71. package/languages/c-macro-invocations.js +380 -0
  72. package/languages/c-preprocessor.js +254 -0
  73. package/languages/csharp.js +1406 -49
  74. package/languages/go.js +1243 -70
  75. package/languages/index.js +728 -6
  76. package/languages/java.js +473 -61
  77. package/languages/javascript.js +1270 -197
  78. package/languages/lexical-scope.js +1280 -0
  79. package/languages/python.js +1557 -72
  80. package/languages/rust-macro-rules.js +1020 -0
  81. package/languages/rust-value-flow.js +416 -0
  82. package/languages/rust.js +2265 -112
  83. package/languages/utils.js +298 -4
  84. package/mcp/server.js +22 -8
  85. package/package.json +1 -1
package/languages/java.js CHANGED
@@ -17,6 +17,7 @@ const {
17
17
  extractJavaDocstring,
18
18
  visitNameNodes,
19
19
  sameNode,
20
+ parseErrorRegions,
20
21
  } = require('./utils');
21
22
  const { PARSE_OPTIONS, safeParse } = require('./index');
22
23
 
@@ -330,20 +331,53 @@ function findFunctions(code, parser) {
330
331
  return functions;
331
332
  }
332
333
 
334
+ const JAVA_LOCAL_SCOPE_BODIES = new Set(['block', 'constructor_body', 'lambda_expression', 'switch_block']);
335
+
336
+ /**
337
+ * Lexical scope of a local type (fix #381): a class, record, enum or
338
+ * interface declared inside a block is visible from its declaration to the
339
+ * end of that block, never as a member of the enclosing type.
340
+ */
341
+ function javaLocalTypeScope(node) {
342
+ for (let p = node.parent; p; p = p.parent) {
343
+ if (p.type === 'class_body' || p.type === 'interface_body' || p.type === 'enum_body' ||
344
+ p.type === 'enum_body_declarations' || p.type === 'program') return {};
345
+ if (JAVA_LOCAL_SCOPE_BODIES.has(p.type)) {
346
+ return {
347
+ lexicalScopeStartLine: node.startPosition.row + 1,
348
+ lexicalScopeEndLine: p.endPosition.row + 1,
349
+ };
350
+ }
351
+ }
352
+ return {};
353
+ }
354
+
333
355
  /**
334
356
  * Process a node for class/interface/enum/record extraction (single-pass helper)
335
357
  * Returns true if node was matched, false otherwise
336
358
  */
337
- function _processClass(node, classes, processedRanges, lines, code) {
338
- const enclosingTypeName = current => {
339
- for (let p = current.parent; p; p = p.parent) {
340
- if (p.type === 'class_declaration' || p.type === 'interface_declaration' ||
341
- p.type === 'enum_declaration' || p.type === 'record_declaration') {
342
- return p.childForFieldName('name')?.text || null;
343
- }
359
+ function enclosingTypeName(current) {
360
+ for (let p = current.parent; p; p = p.parent) {
361
+ // A local class (declared in a method, constructor, initializer
362
+ // or lambda body) is not a member of the enclosing type (fix #381).
363
+ if (JAVA_LOCAL_SCOPE_BODIES.has(p.type)) return null;
364
+ if (JAVA_TYPE_DECLARATIONS.has(p.type)) {
365
+ return p.childForFieldName('name')?.text || null;
344
366
  }
345
- return null;
346
- };
367
+ }
368
+ return null;
369
+ }
370
+
371
+ const JAVA_TYPE_DECLARATIONS = new Set([
372
+ 'class_declaration', 'interface_declaration', 'enum_declaration', 'record_declaration',
373
+ 'annotation_type_declaration',
374
+ ]);
375
+
376
+ function _processClass(node, classes, processedRanges, lines, code) {
377
+ // Only type declarations are processed below: every other node returns
378
+ // before any ancestor climb (fix #388).
379
+ if (!JAVA_TYPE_DECLARATIONS.has(node.type)) return false;
380
+ const localScope = javaLocalTypeScope(node);
347
381
  // Class declarations
348
382
  if (node.type === 'class_declaration') {
349
383
  const rangeKey = `${node.startIndex}-${node.endIndex}`;
@@ -371,9 +405,12 @@ function _processClass(node, classes, processedRanges, lines, code) {
371
405
  name: nameNode.text,
372
406
  startLine,
373
407
  endLine,
408
+ // The name's own line when annotations precede it (fix #389).
409
+ ...(nameNode.startPosition.row + 1 !== startLine && { nameLine: nameNode.startPosition.row + 1 }),
374
410
  type: 'class',
375
411
  members,
376
412
  modifiers,
413
+ ...localScope,
377
414
  ...(isNested && { isNested: true }),
378
415
  ...(enclosingType && { enclosingType }),
379
416
  ...(docstring && { docstring }),
@@ -387,8 +424,11 @@ function _processClass(node, classes, processedRanges, lines, code) {
387
424
  return true;
388
425
  }
389
426
 
390
- // Interface declarations
391
- if (node.type === 'interface_declaration') {
427
+ // Interface declarations. An annotation type (`@interface`) is an
428
+ // interface whose elements are its methods (JLS 9.6), so it is indexed
429
+ // as one (fix #389) with `annotationType` set.
430
+ if (node.type === 'interface_declaration' || node.type === 'annotation_type_declaration') {
431
+ const annotationType = node.type === 'annotation_type_declaration';
392
432
  const rangeKey = `${node.startIndex}-${node.endIndex}`;
393
433
  if (processedRanges.has(rangeKey)) return true;
394
434
  processedRanges.add(rangeKey);
@@ -401,15 +441,19 @@ function _processClass(node, classes, processedRanges, lines, code) {
401
441
  const annotationsWithArgs = extractAnnotationsWithArgs(node);
402
442
  const docstring = extractJavaDocstring(lines, startLine);
403
443
  const generics = extractGenerics(node);
404
- const extendsInfo = extractInterfaceExtends(node);
444
+ const extendsInfo = annotationType ? [] : extractInterfaceExtends(node);
405
445
 
406
446
  classes.push({
407
447
  name: nameNode.text,
408
448
  startLine,
409
449
  endLine,
450
+ // The name's own line when annotations precede it (fix #389).
451
+ ...(nameNode.startPosition.row + 1 !== startLine && { nameLine: nameNode.startPosition.row + 1 }),
410
452
  type: 'interface',
411
453
  members: extractClassMembers(node, lines),
412
454
  modifiers,
455
+ ...(annotationType && { annotationType: true }),
456
+ ...localScope,
413
457
  ...(enclosingTypeName(node) && { enclosingType: enclosingTypeName(node) }),
414
458
  ...(docstring && { docstring }),
415
459
  ...(generics && { generics }),
@@ -434,18 +478,25 @@ function _processClass(node, classes, processedRanges, lines, code) {
434
478
  const annotations = extractAnnotations(node);
435
479
  const annotationsWithArgs = extractAnnotationsWithArgs(node);
436
480
  const docstring = extractJavaDocstring(lines, startLine);
481
+ // An enum implements its interfaces like a class (fix #392): the
482
+ // rename closure and dispatch reach its members through them.
483
+ const implementsInfo = extractImplements(node);
437
484
 
438
485
  classes.push({
439
486
  name: nameNode.text,
440
487
  startLine,
441
488
  endLine,
489
+ // The name's own line when annotations precede it (fix #389).
490
+ ...(nameNode.startPosition.row + 1 !== startLine && { nameLine: nameNode.startPosition.row + 1 }),
442
491
  type: 'enum',
443
492
  members: extractEnumConstants(node, lines),
444
493
  modifiers,
494
+ ...localScope,
445
495
  ...(enclosingTypeName(node) && { enclosingType: enclosingTypeName(node) }),
446
496
  ...(docstring && { docstring }),
447
497
  ...(annotations.length > 0 && { annotations }),
448
- ...(annotationsWithArgs.length > 0 && { annotationsWithArgs })
498
+ ...(annotationsWithArgs.length > 0 && { annotationsWithArgs }),
499
+ ...(implementsInfo.length > 0 && { implements: implementsInfo })
449
500
  });
450
501
  }
451
502
  return true;
@@ -495,9 +546,12 @@ function _processClass(node, classes, processedRanges, lines, code) {
495
546
  name: nameNode.text,
496
547
  startLine,
497
548
  endLine,
549
+ // The name's own line when annotations precede it (fix #389).
550
+ ...(nameNode.startPosition.row + 1 !== startLine && { nameLine: nameNode.startPosition.row + 1 }),
498
551
  type: 'record',
499
552
  members,
500
553
  modifiers,
554
+ ...localScope,
501
555
  ...(enclosingTypeName(node) && { enclosingType: enclosingTypeName(node) }),
502
556
  ...(docstring && { docstring }),
503
557
  ...(generics && { generics }),
@@ -559,13 +613,15 @@ function extractImplements(classNode) {
559
613
  const interfaces = [];
560
614
  for (let i = 0; i < interfacesNode.namedChildCount; i++) {
561
615
  const iface = interfacesNode.namedChild(i);
562
- if (iface.type === 'type_identifier' || iface.type === 'generic_type') {
616
+ if (iface.type === 'type_identifier' || iface.type === 'generic_type' ||
617
+ iface.type === 'scoped_type_identifier') {
563
618
  interfaces.push(iface.text);
564
619
  } else if (iface.type === 'type_list') {
565
620
  // Records and some class declarations wrap interfaces in a type_list
566
621
  for (let j = 0; j < iface.namedChildCount; j++) {
567
622
  const inner = iface.namedChild(j);
568
- if (inner.type === 'type_identifier' || inner.type === 'generic_type') {
623
+ if (inner.type === 'type_identifier' || inner.type === 'generic_type' ||
624
+ inner.type === 'scoped_type_identifier') {
569
625
  interfaces.push(inner.text);
570
626
  }
571
627
  }
@@ -646,6 +702,12 @@ function extractEnumConstants(enumNode, codeOrLines) {
646
702
  const { startLine, endLine } = nodeToLocation(member, code);
647
703
  const modifiers = extractModifiers(member);
648
704
  const returnType = extractReturnType(member);
705
+ // The name's own line and annotations, as for class
706
+ // methods (fix #392: an `@Override` line above the
707
+ // method left its rename on the annotation line).
708
+ const nameLine = nameNode.startPosition.row + 1;
709
+ const annotationsWithArgs = extractAnnotationsWithArgs(member);
710
+ const memberGenerics = extractGenerics(member);
649
711
  constants.push({
650
712
  name: nameNode.text,
651
713
  params: extractJavaParams(paramsNode),
@@ -655,7 +717,10 @@ function extractEnumConstants(enumNode, codeOrLines) {
655
717
  memberType: modifiers.includes('static') ? 'static' : 'method',
656
718
  modifiers,
657
719
  isMethod: true,
658
- ...(returnType && { returnType })
720
+ ...(returnType && { returnType }),
721
+ ...(annotationsWithArgs.length > 0 && { annotationsWithArgs }),
722
+ ...(nameLine !== startLine && { nameLine }),
723
+ ...(memberGenerics && { generics: memberGenerics })
659
724
  });
660
725
  }
661
726
  } else if (member.type === 'constructor_declaration') {
@@ -690,12 +755,15 @@ function extractEnumConstants(enumNode, codeOrLines) {
690
755
  /**
691
756
  * Extract class members (methods, constructors)
692
757
  */
758
+ const JAVA_ELEMENT_ILLEGAL_MODIFIERS = new Set(['default', 'static', 'final', 'private', 'protected',
759
+ 'synchronized', 'native']);
760
+
693
761
  function extractClassMembers(classNode, codeOrLines) {
694
762
  const code = codeOrLines;
695
763
  const members = [];
696
764
  const bodyNode = classNode.childForFieldName('body');
697
765
  if (!bodyNode) return members;
698
- const isInterface = bodyNode.type === 'interface_body';
766
+ const isInterface = bodyNode.type === 'interface_body' || bodyNode.type === 'annotation_type_body';
699
767
 
700
768
  for (let i = 0; i < bodyNode.namedChildCount; i++) {
701
769
  const child = bodyNode.namedChild(i);
@@ -748,6 +816,38 @@ function extractClassMembers(classNode, codeOrLines) {
748
816
  }
749
817
  }
750
818
 
819
+ // Annotation type elements (fix #389): abstract, implicitly public
820
+ // methods of the annotation interface; a `default` value makes the
821
+ // element optional at use sites, never a body.
822
+ if (child.type === 'annotation_type_element_declaration') {
823
+ const nameNode = child.childForFieldName('name');
824
+ if (nameNode) {
825
+ const { startLine, endLine } = nodeToLocation(child, code);
826
+ // Elements take no keyword modifiers besides public/abstract;
827
+ // `default` here introduces the default value.
828
+ const modifiers = extractModifiers(child).filter(modifier =>
829
+ !JAVA_ELEMENT_ILLEGAL_MODIFIERS.has(modifier));
830
+ if (!modifiers.includes('public')) modifiers.push('public');
831
+ if (!modifiers.includes('abstract')) modifiers.push('abstract');
832
+ const returnType = extractReturnType(child);
833
+ const docstring = extractJavaDocstring(code, startLine);
834
+ const nameLine = nameNode.startPosition.row + 1;
835
+ members.push({
836
+ name: nameNode.text,
837
+ params: '',
838
+ paramsStructured: [],
839
+ startLine,
840
+ endLine,
841
+ memberType: 'abstract',
842
+ modifiers,
843
+ isMethod: true,
844
+ ...(returnType && { returnType }),
845
+ ...(docstring && { docstring }),
846
+ ...(nameLine !== startLine && { nameLine }),
847
+ });
848
+ }
849
+ }
850
+
751
851
  // Constructor declarations: intentionally NOT emitted as separate class
752
852
  // members. The class itself is the symbol; `new Foo(...)` calls resolve
753
853
  // to the class via `isConstructor: true` on the call. Emitting the
@@ -758,7 +858,10 @@ function extractClassMembers(classNode, codeOrLines) {
758
858
 
759
859
  // Field declarations: declared types drive receiver disambiguation
760
860
  // (fix #202) — Rust/Go already emit field members with fieldType.
761
- if (child.type === 'field_declaration') {
861
+ // Interface and annotation type constants (`constant_declaration`,
862
+ // implicitly public static final; fix #389) are fields like class
863
+ // fields.
864
+ if (child.type === 'field_declaration' || child.type === 'constant_declaration') {
762
865
  const typeNode = child.childForFieldName('type');
763
866
  const fieldTypeText = typeNode ? typeNode.text : null;
764
867
  // Visibility travels with the member (fix #251 — public
@@ -766,16 +869,25 @@ function extractClassMembers(classNode, codeOrLines) {
766
869
  // the member had no modifiers for the #240 discipline to read;
767
870
  // the #241 Rust-field twin).
768
871
  const fieldModifiers = extractModifiers(child);
872
+ if (child.type === 'constant_declaration') {
873
+ for (const implicit of ['public', 'static', 'final']) {
874
+ if (!fieldModifiers.includes(implicit)) fieldModifiers.push(implicit);
875
+ }
876
+ }
769
877
  for (let j = 0; j < child.namedChildCount; j++) {
770
878
  const decl = child.namedChild(j);
771
879
  if (decl.type === 'variable_declarator') {
772
880
  const nameNode = decl.childForFieldName('name');
773
881
  if (nameNode && fieldTypeText) {
774
882
  const { startLine, endLine } = nodeToLocation(child, code);
883
+ // fix #390: an annotation on its own line starts the
884
+ // declaration; the name's line is where edits go.
885
+ const nameLine = nameNode.startPosition.row + 1;
775
886
  members.push({
776
887
  name: nameNode.text,
777
888
  startLine,
778
889
  endLine,
890
+ ...(nameLine !== startLine && { nameLine }),
779
891
  memberType: 'field',
780
892
  ...(fieldModifiers.length > 0 && { modifiers: fieldModifiers }),
781
893
  fieldType: fieldTypeText
@@ -880,12 +992,55 @@ function parse(code, parser) {
880
992
  functions,
881
993
  classes,
882
994
  stateObjects,
883
- ...(tree.rootNode.hasError && { parseRecovery: true }),
995
+ ...(tree.rootNode.hasError && { parseRecovery: true, parseErrorRegions: parseErrorRegions(tree.rootNode) }),
884
996
  imports: [],
885
997
  exports: []
886
998
  };
887
999
  }
888
1000
 
1001
+ /**
1002
+ * Call records for annotation element sites (fix #389): an
1003
+ * `element_value_pair` key names the element method of the annotation type
1004
+ * (`@Marker(priority = 2)`); a single-argument list without a key sets the
1005
+ * element `value` (`@Marker("x")`) with no name token (`implicitName`).
1006
+ */
1007
+ function javaAnnotationElementSites(node, calls, enclosingFunctionOf) {
1008
+ const annotation = node.type === 'element_value_pair' ? node.parent?.parent : node.parent;
1009
+ if (annotation?.type !== 'annotation') return;
1010
+ const typeNode = annotation.childForFieldName('name');
1011
+ const typeName = typeNode?.type === 'scoped_identifier'
1012
+ ? typeNode.childForFieldName('name')?.text : typeNode?.text;
1013
+ if (!typeName) return;
1014
+ const typeQualifier = typeNode.type === 'scoped_identifier'
1015
+ ? typeNode.childForFieldName('scope')?.text : null;
1016
+ let nameNode = null;
1017
+ let argument = null;
1018
+ if (node.type === 'element_value_pair') {
1019
+ nameNode = node.childForFieldName('key');
1020
+ if (nameNode?.type !== 'identifier') return;
1021
+ } else {
1022
+ const args = node.namedChildren.filter(child => !child.type.includes('comment'));
1023
+ if (args.length !== 1 || args[0].type === 'element_value_pair') return;
1024
+ argument = args[0];
1025
+ }
1026
+ calls.push({
1027
+ callSite: typeOrigin('call', nameNode || typeNode),
1028
+ name: nameNode ? nameNode.text : 'value',
1029
+ line: (nameNode || argument).startPosition.row + 1,
1030
+ // Where `value = ` goes when the element is renamed.
1031
+ ...(argument && { argColumn: argument.startPosition.column }),
1032
+ isMethod: true,
1033
+ receiverType: typeName,
1034
+ receiverTypeSource: 'annotation',
1035
+ receiverTypeEvidence: typeOrigin('annotation', typeNode),
1036
+ ...(typeQualifier && { receiverTypeQualifier: typeQualifier }),
1037
+ annotationElement: true,
1038
+ ...(!nameNode && { implicitName: true }),
1039
+ argCount: 0,
1040
+ enclosingFunction: enclosingFunctionOf(),
1041
+ });
1042
+ }
1043
+
889
1044
  /**
890
1045
  * Find all function/method calls in Java code using tree-sitter AST
891
1046
  * @param {string} code - Source code to analyze
@@ -948,7 +1103,7 @@ function findCallsInCode(code, parser) {
948
1103
  };
949
1104
  const JAVA_TYPE_DECLARATIONS = new Set([
950
1105
  'class_declaration', 'interface_declaration',
951
- 'enum_declaration', 'record_declaration',
1106
+ 'enum_declaration', 'record_declaration', 'annotation_type_declaration',
952
1107
  ]);
953
1108
 
954
1109
  // Extract type name from a Java type node (strips generics, qualified names)
@@ -1017,6 +1172,19 @@ function findCallsInCode(code, parser) {
1017
1172
  }
1018
1173
  return lexicalNestedTypeOwner(typeNode, extractTypeName(typeNode));
1019
1174
  };
1175
+ // A fully qualified type spelling `a.b.Node` names package `a.b` (fix
1176
+ // #394): Java has no relative package names, so the written prefix is
1177
+ // the whole package. Recorded where no nested owner qualifies the type.
1178
+ const extractTypePackage = (typeNode) => {
1179
+ if (!typeNode) return null;
1180
+ const text = String(typeNode.text || '').replace(/<.*$/s, '').replace(/(?:\[\s*\]\s*)+$/, '').trim();
1181
+ const parts = text.split('.').map(part => part.trim());
1182
+ if (parts.length < 2) return null;
1183
+ const prefix = parts.slice(0, -1);
1184
+ return prefix.every(part => /^[a-z_][A-Za-z0-9_$]*$/.test(part)) ? prefix.join('.') : null;
1185
+ };
1186
+ const extractReceiverTypeQualifier = (typeNode) =>
1187
+ extractTypeQualifier(typeNode) || extractTypePackage(typeNode);
1020
1188
  const qualifyTypeName = (text) => {
1021
1189
  const raw = String(text || '').trim()
1022
1190
  .replace(/^\?\s+extends\s+/, '')
@@ -1031,7 +1199,114 @@ function findCallsInCode(code, parser) {
1031
1199
  return base;
1032
1200
  };
1033
1201
 
1202
+ // The base type of the anonymous class whose body holds `node`, when the
1203
+ // nearest class body is an anonymous one (`new StrBuilder() { .. }`):
1204
+ // `super` there names that type (fix #394).
1205
+ // Does the anonymous (or enum-constant) body around `node` declare a
1206
+ // method of this name itself (fix #401)?
1207
+ const anonymousBodyDeclares = (node, name) => {
1208
+ for (let p = node.parent; p; p = p.parent) {
1209
+ if (p.type === 'class_body') {
1210
+ return p.namedChildren.some(member => member.type === 'method_declaration' &&
1211
+ member.childForFieldName('name')?.text === name);
1212
+ }
1213
+ if (JAVA_TYPE_DECLARATIONS.has(p.type)) return false;
1214
+ }
1215
+ return false;
1216
+ };
1217
+ // The anonymous body's supertype as written, qualifier kept (fix #401).
1218
+ const anonymousSuperTypeText = (node) => {
1219
+ for (let p = node.parent; p; p = p.parent) {
1220
+ if (p.type === 'class_body') {
1221
+ const owner = p.parent;
1222
+ if (owner?.type === 'enum_constant') {
1223
+ const enumDeclaration = owner.parent?.parent;
1224
+ return enumDeclaration?.type === 'enum_declaration'
1225
+ ? enumDeclaration.childForFieldName('name')?.text || null : null;
1226
+ }
1227
+ const typeNode = owner?.type === 'object_creation_expression' ? owner.childForFieldName('type') : null;
1228
+ return typeNode ? typeNode.text.replace(/<[^]*$/, '').replace(/\s+/g, '') || null : null;
1229
+ }
1230
+ if (JAVA_TYPE_DECLARATIONS.has(p.type)) return null;
1231
+ }
1232
+ return null;
1233
+ };
1234
+ const anonymousSuperType = (node) => {
1235
+ for (let p = node.parent; p; p = p.parent) {
1236
+ if (p.type === 'class_body') {
1237
+ const owner = p.parent;
1238
+ // An enum constant's body is an anonymous subclass of its
1239
+ // enum (fix #401): `super.m()` there is the enum's member.
1240
+ if (owner?.type === 'enum_constant') {
1241
+ const enumDeclaration = owner.parent?.parent;
1242
+ return enumDeclaration?.type === 'enum_declaration'
1243
+ ? enumDeclaration.childForFieldName('name')?.text || null : null;
1244
+ }
1245
+ return owner?.type === 'object_creation_expression'
1246
+ ? extractTypeName(owner.childForFieldName('type')) || null : null;
1247
+ }
1248
+ if (JAVA_TYPE_DECLARATIONS.has(p.type)) return null;
1249
+ }
1250
+ return null;
1251
+ };
1252
+
1253
+ // A written type as an argument kind type: qualified element type plus
1254
+ // its array dimensions (fix #394; `Map<K, V[]>[]` keeps one).
1255
+ const arrayArgType = (text) => {
1256
+ const raw = String(text || '').trim();
1257
+ let outer = '';
1258
+ let depth = 0;
1259
+ for (const ch of raw) {
1260
+ if (ch === '<') depth++;
1261
+ else if (ch === '>') depth--;
1262
+ else if (depth === 0) outer += ch;
1263
+ }
1264
+ const trailing = outer.match(/(?:\[\s*\]\s*)+$/);
1265
+ const dims = trailing ? (trailing[0].match(/\[/g) || []).length : 0;
1266
+ const element = qualifyTypeName(raw.replace(/(?:\[\s*\]\s*)+$/, ''));
1267
+ return element ? element + '[]'.repeat(dims) : null;
1268
+ };
1269
+
1034
1270
  // Build type map from method/constructor parameters
1271
+ // One-hop `var` aliases (fix #381): `var c = this.config; c.load()`
1272
+ // receives through `this.config`; `var c = cfg;` carries cfg's type.
1273
+ // name -> { node, scopeStart } for effectively-final locals only.
1274
+ const localFieldAliases = new Map();
1275
+ const assignedNamesByFn = new Map();
1276
+ const reassignedIn = (fnNode, name) => {
1277
+ let names = assignedNamesByFn.get(fnNode.id);
1278
+ if (!names) {
1279
+ names = new Set();
1280
+ const walk = (n) => {
1281
+ for (const c of n.namedChildren) {
1282
+ if (c.type === 'assignment_expression') {
1283
+ const left = c.childForFieldName('left');
1284
+ if (left?.type === 'identifier') names.add(left.text);
1285
+ } else if (c.type === 'update_expression') {
1286
+ const target = c.namedChildren.find(x => x.type === 'identifier');
1287
+ if (target) names.add(target.text);
1288
+ }
1289
+ walk(c);
1290
+ }
1291
+ };
1292
+ walk(fnNode);
1293
+ assignedNamesByFn.set(fnNode.id, names);
1294
+ }
1295
+ return names.has(name);
1296
+ };
1297
+ const enclosingFunctionNode = (n) => {
1298
+ for (let p = n.parent; p; p = p.parent) if (isFunctionNode(p)) return p;
1299
+ return null;
1300
+ };
1301
+
1302
+ // Array dimensions written on a declarator (`int x[]`, `String a[]`).
1303
+ const declaratorDims = (node) => {
1304
+ let dims = '';
1305
+ for (const child of node?.namedChildren || []) {
1306
+ if (child.type === 'dimensions') dims += '[]'.repeat((child.text.match(/\[/g) || []).length);
1307
+ }
1308
+ return dims;
1309
+ };
1035
1310
  const buildScopeTypeMap = (node) => {
1036
1311
  const typeMap = new ReceiverTypeMap();
1037
1312
  const rawTypeMap = new Map();
@@ -1046,8 +1321,9 @@ function findCallsInCode(code, parser) {
1046
1321
  const typeName = extractTypeName(typeNode);
1047
1322
  if (nameNode && typeName) {
1048
1323
  typeMap.set(nameNode.text, typeName, 'annotation', typeNode);
1049
- rawTypeMap.set(nameNode.text, typeNode.text);
1050
- const qualifier = extractTypeQualifier(typeNode);
1324
+ // `String a[]` is a String[] (fix #394).
1325
+ rawTypeMap.set(nameNode.text, typeNode.text + declaratorDims(param));
1326
+ const qualifier = extractReceiverTypeQualifier(typeNode);
1051
1327
  if (qualifier) qualifierMap.set(nameNode.text, qualifier);
1052
1328
  }
1053
1329
  }
@@ -1087,6 +1363,13 @@ function findCallsInCode(code, parser) {
1087
1363
  }
1088
1364
  return undefined;
1089
1365
  };
1366
+ const getReceiverTypeOrigin = (varName) => {
1367
+ for (let i = functionStack.length - 1; i >= 0; i--) {
1368
+ const typeMap = scopeTypes.get(functionStack[i].startLine);
1369
+ if (typeMap?.has(varName)) return typeMap.origins.get(varName);
1370
+ }
1371
+ return undefined;
1372
+ };
1090
1373
  const getReceiverRawType = (varName) => {
1091
1374
  for (let i = functionStack.length - 1; i >= 0; i--) {
1092
1375
  const typeMap = scopeRawTypes.get(functionStack[i].startLine);
@@ -1194,7 +1477,7 @@ function findCallsInCode(code, parser) {
1194
1477
  }
1195
1478
  return false;
1196
1479
  };
1197
- const enclosingFieldTypeNode = (n, name) => {
1480
+ const enclosingFieldDeclaration = (n, name) => {
1198
1481
  for (let p = n.parent; p; p = p.parent) {
1199
1482
  if (!JAVA_TYPE_DECLARATIONS.has(p.type)) continue;
1200
1483
  const body = p.childForFieldName('body');
@@ -1206,7 +1489,8 @@ function findCallsInCode(code, parser) {
1206
1489
  const decl = child.namedChild(j);
1207
1490
  if (decl.type === 'variable_declarator' &&
1208
1491
  decl.childForFieldName('name')?.text === name) {
1209
- return child.childForFieldName('type') || null;
1492
+ const type = child.childForFieldName('type');
1493
+ return type ? { type, dims: declaratorDims(decl) } : null;
1210
1494
  }
1211
1495
  }
1212
1496
  }
@@ -1280,24 +1564,38 @@ function findCallsInCode(code, parser) {
1280
1564
  const tn = arg.childForFieldName('type');
1281
1565
  return tn ? `new:${qualifyTypeName(tn.text)}` : 'expr';
1282
1566
  }
1567
+ case 'array_creation_expression': {
1568
+ // `new String[3][]`, `new String[]{..}`: an array of that
1569
+ // many dimensions (fix #394).
1570
+ const tn = arg.childForFieldName('type');
1571
+ let dims = 0;
1572
+ for (const child of arg.namedChildren) {
1573
+ if (child.type === 'dimensions_expr') dims++;
1574
+ else if (child.type === 'dimensions') dims += (child.text.match(/\[/g) || []).length;
1575
+ }
1576
+ const element = tn && qualifyTypeName(tn.text);
1577
+ return element && dims > 0 ? `new:${element}${'[]'.repeat(dims)}` : 'expr';
1578
+ }
1283
1579
  case 'cast_expression': {
1284
1580
  const tn = arg.childForFieldName('type');
1285
- return tn ? `cast:${qualifyTypeName(tn.text)}` : 'expr';
1581
+ const type = tn && arrayArgType(tn.text);
1582
+ return type ? `cast:${type}` : 'expr';
1286
1583
  }
1287
1584
  case 'class_literal': {
1288
1585
  const tn = arg.namedChild(0);
1289
1586
  return tn ? `class:${bareTypeName(tn.text)}` : 'class:Object';
1290
1587
  }
1291
1588
  case 'identifier': {
1292
- const fieldType = !isDeclaredLocal(arg.text)
1293
- ? enclosingFieldTypeNode(arg, arg.text) : null;
1589
+ const field = !isDeclaredLocal(arg.text)
1590
+ ? enclosingFieldDeclaration(arg, arg.text) : null;
1294
1591
  const typeName = getReceiverType(arg.text) ||
1295
- extractTypeName(fieldType);
1592
+ extractTypeName(field?.type);
1296
1593
  const rawType = getReceiverRawType(arg.text) ||
1297
- fieldType?.text;
1298
- return typeName
1299
- ? `type:${qualifyTypeName(rawType || typeName)}`
1300
- : 'expr';
1594
+ (field ? field.type.text + field.dims : null);
1595
+ // Array dimensions stay on the kind (fix #394): a String[]
1596
+ // never binds a String parameter, a String never an Object[].
1597
+ const type = typeName ? arrayArgType(rawType || typeName) : null;
1598
+ return type ? `type:${type}` : 'expr';
1301
1599
  }
1302
1600
  case 'field_access': {
1303
1601
  // A class-qualified static field has a compiler-visible
@@ -1414,8 +1712,9 @@ function findCallsInCode(code, parser) {
1414
1712
  if (!arrayNode) return 'expr';
1415
1713
  if (arrayNode.type === 'identifier') {
1416
1714
  const rawType = getReceiverRawType(arrayNode.text);
1417
- if (rawType?.endsWith('[]')) {
1418
- return `type:${qualifyTypeName(rawType.slice(0, -2))}`;
1715
+ if (rawType?.trim().endsWith(']')) {
1716
+ const type = arrayArgType(rawType.trim().replace(/\[\s*\]$/, ''));
1717
+ if (type) return `type:${type}`;
1419
1718
  }
1420
1719
  }
1421
1720
  const containerKind = argKindOf(arrayNode);
@@ -1423,8 +1722,27 @@ function findCallsInCode(code, parser) {
1423
1722
  }
1424
1723
  case 'parenthesized_expression':
1425
1724
  return arg.namedChildCount === 1 ? argKindOf(arg.namedChild(0)) : 'expr';
1426
- case 'lambda_expression':
1725
+ case 'lambda_expression': {
1726
+ // Its parameter count selects among functional-interface
1727
+ // overloads (fix #391).
1728
+ const params = arg.childForFieldName('parameters');
1729
+ if (!params) return 'lambda';
1730
+ if (params.type === 'identifier') return 'lambda:1';
1731
+ return `lambda:${params.namedChildren.filter(child =>
1732
+ child.type === 'identifier' || child.type === 'formal_parameter' ||
1733
+ child.type === 'spread_parameter').length}`;
1734
+ }
1427
1735
  case 'method_reference': return 'lambda';
1736
+ case 'binary_expression': {
1737
+ // String concatenation (JLS 15.18.1): `+` with a String
1738
+ // operand is a String (fix #390, `addStatement("a" + "b")`
1739
+ // selects the String overload, never addStatement(CodeBlock)).
1740
+ if (arg.childForFieldName('operator')?.text !== '+') return 'expr';
1741
+ const left = arg.childForFieldName('left');
1742
+ const right = arg.childForFieldName('right');
1743
+ return (left && argKindOf(left) === 'string') ||
1744
+ (right && argKindOf(right) === 'string') ? 'string' : 'expr';
1745
+ }
1428
1746
  case 'unary_expression':
1429
1747
  // -1, -2.5 — numeric literal kinds survive negation
1430
1748
  return arg.namedChildCount === 1 ? argKindOf(arg.namedChild(0)) : 'expr';
@@ -1466,7 +1784,7 @@ function findCallsInCode(code, parser) {
1466
1784
  const typeNode = node.childForFieldName('type');
1467
1785
  const nameNode = node.childForFieldName('name');
1468
1786
  const typeName = extractTypeName(typeNode);
1469
- const typeQualifier = extractTypeQualifier(typeNode);
1787
+ const typeQualifier = extractReceiverTypeQualifier(typeNode);
1470
1788
  const scopeKey = functionStack[functionStack.length - 1].startLine;
1471
1789
  const typeMap = scopeTypes.get(scopeKey);
1472
1790
  if (nameNode && typeName && typeMap) {
@@ -1491,7 +1809,15 @@ function findCallsInCode(code, parser) {
1491
1809
  // Handle method invocations: foo(), obj.foo(), this.foo()
1492
1810
  if (node.type === 'method_invocation') {
1493
1811
  const nameNode = node.childForFieldName('name');
1494
- const objNode = node.childForFieldName('object');
1812
+ let objNode = node.childForFieldName('object');
1813
+ // fix #381: `c.load()` after `var c = this.config` receives like
1814
+ // `this.config.load()` (same function, effectively final).
1815
+ if (objNode?.type === 'identifier' && localFieldAliases.has(objNode.text) &&
1816
+ !getReceiverType(objNode.text) &&
1817
+ localFieldAliases.get(objNode.text).scopeStart ===
1818
+ functionStack[functionStack.length - 1]?.startLine) {
1819
+ objNode = localFieldAliases.get(objNode.text).node;
1820
+ }
1495
1821
 
1496
1822
  if (nameNode) {
1497
1823
  const enclosingFunction = getCurrentEnclosingFunction();
@@ -1506,8 +1832,10 @@ function findCallsInCode(code, parser) {
1506
1832
  const valueNode = receiverNode.childForFieldName('value');
1507
1833
  if (valueNode?.type === 'identifier') castReceiverName = valueNode.text;
1508
1834
  }
1835
+ // `super.m()` names the direct superclass's member (fix #394).
1509
1836
  let receiver = castReceiverName ||
1510
- ((receiverNode?.type === 'identifier' || receiverNode?.type === 'this')
1837
+ ((receiverNode?.type === 'identifier' || receiverNode?.type === 'this' ||
1838
+ receiverNode?.type === 'super')
1511
1839
  ? receiverNode.text : undefined);
1512
1840
  // fix #353: `beta.Helper.widget()` — a lowercase dotted root
1513
1841
  // under a capitalized member is a PACKAGE-qualified type
@@ -1531,9 +1859,28 @@ function findCallsInCode(code, parser) {
1531
1859
  }
1532
1860
  const inlineConstructorType = receiverNode?.type === 'object_creation_expression'
1533
1861
  ? extractTypeName(receiverNode.childForFieldName('type')) : undefined;
1534
- const receiverType = castReceiverType || inlineConstructorType ||
1862
+ // Declared array element (fix #359): `items[0].m()` with
1863
+ // `Conv[] items` dispatches on Conv. One dimension only; a
1864
+ // multi-dimensional access that yields an array abstains.
1865
+ let arrayElementType;
1866
+ let arrayElementQualifier;
1867
+ if (!receiver && receiverNode?.type === 'array_access') {
1868
+ const arrayNode = receiverNode.childForFieldName('array');
1869
+ const rawType = arrayNode?.type === 'identifier'
1870
+ ? getReceiverRawType(arrayNode.text)?.trim() : undefined;
1871
+ if (rawType && rawType.endsWith('[]')) {
1872
+ const elementText = rawType.slice(0, -2).trim();
1873
+ if (elementText && !elementText.includes('[') &&
1874
+ !elementText.includes('<')) {
1875
+ const parts = elementText.split('.');
1876
+ arrayElementType = parts.pop();
1877
+ if (parts.length > 0) arrayElementQualifier = parts.join('.');
1878
+ }
1879
+ }
1880
+ }
1881
+ const receiverType = castReceiverType || inlineConstructorType || arrayElementType ||
1535
1882
  ((receiver && receiver !== 'this') ? getReceiverType(receiver) : undefined);
1536
- const receiverTypeQualifier = packageQualifier ||
1883
+ const receiverTypeQualifier = packageQualifier || arrayElementQualifier ||
1537
1884
  (!castReceiverType && receiver ? getReceiverTypeQualifier(receiver) : undefined);
1538
1885
  const receiverIsTypeQualified = !!((receiverNode?.type === 'identifier' || packageQualifier) &&
1539
1886
  receiver && /^[A-Z]/.test(receiver) && !receiverType &&
@@ -1564,8 +1911,11 @@ function findCallsInCode(code, parser) {
1564
1911
  receiverRoot = rootNode.text;
1565
1912
  receiverFieldName = fldNode.text;
1566
1913
  receiverRootType = rootType;
1567
- receiverRootNamespace =
1568
- getReceiverTypeQualifier(rootNode.text);
1914
+ // A nested owner only: the root's package
1915
+ // does not qualify the field's type.
1916
+ const rootQualifier = getReceiverTypeQualifier(rootNode.text);
1917
+ receiverRootNamespace = /^[A-Z]/.test(rootQualifier || '')
1918
+ ? rootQualifier : undefined;
1569
1919
  }
1570
1920
  }
1571
1921
  }
@@ -1614,9 +1964,16 @@ function findCallsInCode(code, parser) {
1614
1964
  ...(receiverType && { receiverType, ...(getReceiverType(receiver, true) ||
1615
1965
  (castReceiverType ? { receiverTypeSource: 'cast', receiverTypeEvidence: typeOrigin('cast', objNode) } :
1616
1966
  inlineConstructorType ? { receiverTypeSource: 'constructor', receiverTypeEvidence: typeOrigin('constructor', receiverNode) } :
1967
+ arrayElementType ? { receiverTypeSource: 'annotation', receiverTypeEvidence: typeOrigin('annotation', receiverNode) } :
1617
1968
  { receiverTypeSource: 'unknown' })) }),
1618
1969
  ...(receiverTypeQualifier && { receiverTypeQualifier }),
1619
1970
  ...(receiverIsTypeQualified && { receiverIsTypeQualified: true }),
1971
+ ...(receiver === 'super' && anonymousSuperType(node) && { receiverSuperType: anonymousSuperType(node) }),
1972
+ // A bare call in an anonymous class or enum-constant
1973
+ // body: its implicit `this` is that body's class (fix
1974
+ // #401), whose supertype's members come first.
1975
+ ...(!objNode && anonymousSuperTypeText(node) && { anonymousSuperType: anonymousSuperTypeText(node),
1976
+ ...(anonymousBodyDeclares(node, nameNode.text) && { anonymousDeclares: true }) }),
1620
1977
  ...(castReceiverType && { receiverTypeCast: true }),
1621
1978
  ...(receiverFieldName && { receiverRoot, receiverField: receiverFieldName }),
1622
1979
  ...(receiverFieldName && receiverRootType && { receiverRootType }),
@@ -1629,6 +1986,10 @@ function findCallsInCode(code, parser) {
1629
1986
  ...(receiverCallTypePath && { receiverCallTypePath }),
1630
1987
  argCount: callArgs.argCount,
1631
1988
  ...(callArgs.argKinds && { argKinds: callArgs.argKinds }),
1989
+ // `obj.<T>m()`: explicit method type arguments (fix #391).
1990
+ ...(node.childForFieldName('type_arguments') && {
1991
+ methodTypeArgs: node.childForFieldName('type_arguments').namedChildCount,
1992
+ }),
1632
1993
  ...(assignedTo && { assignedTo }),
1633
1994
  enclosingFunction,
1634
1995
  ...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp })
@@ -1751,13 +2112,26 @@ function findCallsInCode(code, parser) {
1751
2112
  return true;
1752
2113
  }
1753
2114
 
2115
+ // Annotation element sites (fix #389): `@Marker(priority = 2)` and
2116
+ // the single-element shorthand `@Marker("x")`.
2117
+ if (node.type === 'element_value_pair' || node.type === 'annotation_argument_list') {
2118
+ javaAnnotationElementSites(node, calls, getCurrentEnclosingFunction);
2119
+ if (node.type === 'element_value_pair') return true;
2120
+ }
2121
+
1754
2122
  // Detect method references passed as arguments: this::worker, obj::method
1755
2123
  if (node.type === 'method_reference') {
1756
2124
  const nameNode = node.namedChild(node.namedChildCount - 1);
1757
2125
  const objNode = node.namedChild(0);
1758
2126
  if (nameNode && nameNode.type === 'identifier') {
1759
- const receiver = objNode ? (objNode.type === 'identifier' || objNode.type === 'this' ? objNode.text : undefined) : undefined;
1760
- const receiverType = (receiver && receiver !== 'this') ? getReceiverType(receiver) : undefined;
2127
+ // `obj::m`, `this::m`, `super::m` (fix #395: `super::m` was a
2128
+ // bare name) and qualified `a.b::m` all reference a method.
2129
+ const receiver = objNode && !sameNode(objNode, nameNode)
2130
+ ? (['identifier', 'this', 'super'].includes(objNode.type) ? objNode.text
2131
+ : objNode.text.replace(/\s+/g, '') || undefined)
2132
+ : undefined;
2133
+ const receiverType = (receiver && receiver !== 'this' && receiver !== 'super' &&
2134
+ objNode.type === 'identifier') ? getReceiverType(receiver) : undefined;
1761
2135
  const enclosingFunction = getCurrentEnclosingFunction();
1762
2136
  calls.push({
1763
2137
  callSite: typeOrigin('call', nameNode),
@@ -1783,30 +2157,65 @@ function findCallsInCode(code, parser) {
1783
2157
  const declaredType = declTypeNode && declTypeNode.text !== 'var'
1784
2158
  ? extractTypeName(declTypeNode) : null;
1785
2159
  const declaredTypeQualifier = declTypeNode && declTypeNode.text !== 'var'
1786
- ? extractTypeQualifier(declTypeNode) : null;
2160
+ ? extractReceiverTypeQualifier(declTypeNode) : null;
1787
2161
  for (let i = 0; i < node.namedChildCount; i++) {
1788
2162
  const child = node.namedChild(i);
1789
2163
  if (child.type === 'variable_declarator') {
1790
2164
  const nameNode = child.childForFieldName('name');
1791
2165
  const valueNode = child.childForFieldName('value');
1792
- // new Type() is the DYNAMIC type — more precise than the
1793
- // declared static type (Foo f = new Bar() dispatches to Bar)
1794
- let typeName = valueNode?.type === 'object_creation_expression'
2166
+ // The declared type is the receiver's static type: `Shape s
2167
+ // = new Circle(); s.area()` binds Shape.area and reaches
2168
+ // Circle.area by dispatch (fix #394). `var` takes the
2169
+ // constructed type; a declaration of the constructed
2170
+ // type itself keeps the exact constructor evidence.
2171
+ const constructedType = valueNode?.type === 'object_creation_expression'
1795
2172
  ? extractTypeName(valueNode.childForFieldName('type'))
1796
2173
  : null;
1797
- let typeQualifier = valueNode?.type === 'object_creation_expression'
1798
- ? extractTypeQualifier(valueNode.childForFieldName('type'))
2174
+ const constructedQualifier = valueNode?.type === 'object_creation_expression'
2175
+ ? extractReceiverTypeQualifier(valueNode.childForFieldName('type'))
1799
2176
  : null;
1800
- if (!typeName) typeName = declaredType;
1801
- if (!typeQualifier) typeQualifier = declaredTypeQualifier;
2177
+ const constructorTyped = !!constructedType &&
2178
+ (!declaredType || (constructedType === declaredType &&
2179
+ (constructedQualifier || null) === (declaredTypeQualifier || null)));
2180
+ let typeName = declaredType || constructedType;
2181
+ let typeQualifier = declaredType ? declaredTypeQualifier : constructedQualifier;
2182
+ // fix #381: an effectively-final `var` alias.
2183
+ if (nameNode) localFieldAliases.delete(nameNode.text);
2184
+ const fnNode = !typeName && nameNode && valueNode && declTypeNode?.text === 'var'
2185
+ ? enclosingFunctionNode(node) : null;
2186
+ if (fnNode && valueNode.text !== nameNode.text && !reassignedIn(fnNode, nameNode.text)) {
2187
+ const scopeKey = functionStack[functionStack.length - 1].startLine;
2188
+ if (valueNode.type === 'identifier' && getReceiverType(valueNode.text)) {
2189
+ scopeTypes.get(scopeKey)?.set(nameNode.text, getReceiverType(valueNode.text),
2190
+ getReceiverTypeOrigin(valueNode.text) || 'flow');
2191
+ const qualifier = getReceiverTypeQualifier(valueNode.text);
2192
+ if (qualifier) scopeTypeQualifiers.get(scopeKey)?.set(nameNode.text, qualifier);
2193
+ const raw = getReceiverRawType(valueNode.text);
2194
+ if (raw) scopeRawTypes.get(scopeKey)?.set(nameNode.text, raw);
2195
+ } else if ((valueNode.type === 'field_access' &&
2196
+ valueNode.childForFieldName('object')?.type === 'this' &&
2197
+ valueNode.childForFieldName('field')?.type === 'identifier') ||
2198
+ (valueNode.type === 'identifier' && !isDeclaredLocal(valueNode.text) &&
2199
+ hasEnclosingField(node, valueNode.text))) {
2200
+ localFieldAliases.set(nameNode.text, { node: valueNode, scopeStart: scopeKey });
2201
+ }
2202
+ }
1802
2203
  if (nameNode && typeName) {
1803
2204
  const scopeKey = functionStack[functionStack.length - 1].startLine;
1804
2205
  const typeMap = scopeTypes.get(scopeKey);
1805
- if (typeMap) typeMap.set(nameNode.text, typeName, valueNode?.type === 'object_creation_expression' ? 'constructor' : 'annotation', valueNode || declTypeNode);
2206
+ // A local never reassigned holds exactly the constructed
2207
+ // value: dispatch reaches that type's member, never an
2208
+ // unrelated implementation of the declared type.
2209
+ const fnOfLocal = constructedType && !constructorTyped ? enclosingFunctionNode(node) : null;
2210
+ const origin = fnOfLocal && !reassignedIn(fnOfLocal, nameNode.text)
2211
+ ? { ...typeOrigin('annotation', declTypeNode), constructedType } : null;
2212
+ if (typeMap) typeMap.set(nameNode.text, typeName,
2213
+ origin || (constructorTyped ? 'constructor' : 'annotation'),
2214
+ constructorTyped ? valueNode : declTypeNode);
1806
2215
  const rawTypeMap = scopeRawTypes.get(scopeKey);
1807
- const rawType = valueNode?.type === 'object_creation_expression'
2216
+ const rawType = constructorTyped
1808
2217
  ? valueNode.childForFieldName('type')?.text
1809
- : declTypeNode?.text;
2218
+ : declTypeNode?.text && declTypeNode.text + declaratorDims(child);
1810
2219
  if (rawType) rawTypeMap?.set(nameNode.text, rawType);
1811
2220
  const qualifierMap = scopeTypeQualifiers.get(scopeKey);
1812
2221
  if (typeQualifier) qualifierMap?.set(nameNode.text, typeQualifier);
@@ -1827,11 +2236,11 @@ function findCallsInCode(code, parser) {
1827
2236
  ? extractTypeName(resValueNode.childForFieldName('type'))
1828
2237
  : null;
1829
2238
  let typeQualifier = resValueNode?.type === 'object_creation_expression'
1830
- ? extractTypeQualifier(resValueNode.childForFieldName('type'))
2239
+ ? extractReceiverTypeQualifier(resValueNode.childForFieldName('type'))
1831
2240
  : null;
1832
2241
  if (!typeName && resTypeNode && resTypeNode.text !== 'var') {
1833
2242
  typeName = extractTypeName(resTypeNode);
1834
- typeQualifier = extractTypeQualifier(resTypeNode);
2243
+ typeQualifier = extractReceiverTypeQualifier(resTypeNode);
1835
2244
  }
1836
2245
  if (resNameNode && typeName) {
1837
2246
  const scopeKey = functionStack[functionStack.length - 1].startLine;
@@ -1958,8 +2367,9 @@ function findExportsInCode(code, parser) {
1958
2367
  return false; // Don't descend into class body
1959
2368
  }
1960
2369
 
1961
- // Public interfaces
1962
- if (node.type === 'interface_declaration' && isPublic(node)) {
2370
+ // Public interfaces (annotation types are interfaces, fix #389)
2371
+ if ((node.type === 'interface_declaration' || node.type === 'annotation_type_declaration') &&
2372
+ isPublic(node)) {
1963
2373
  const nameNode = node.childForFieldName('name');
1964
2374
  if (nameNode) {
1965
2375
  exports.push({
@@ -2063,8 +2473,10 @@ function findUsagesInCode(code, name, parser, tree) {
2063
2473
  sameNode(parent.childForFieldName('name'), node)) {
2064
2474
  usageType = 'definition';
2065
2475
  }
2066
- // Definition: interface name
2067
- else if (parent.type === 'interface_declaration' &&
2476
+ // Definition: interface / annotation type / annotation element name
2477
+ else if ((parent.type === 'interface_declaration' ||
2478
+ parent.type === 'annotation_type_declaration' ||
2479
+ parent.type === 'annotation_type_element_declaration') &&
2068
2480
  sameNode(parent.childForFieldName('name'), node)) {
2069
2481
  usageType = 'definition';
2070
2482
  }