ucn 5.5.1 → 5.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/.claude/skills/ucn/SKILL.md +35 -9
  2. package/.claude/skills/ucn/references/commands.md +40 -18
  3. package/.claude/skills/ucn/references/resolution-rules.md +126 -20
  4. package/.claude/skills/ucn/references/trust-contract.md +5 -3
  5. package/README.md +32 -21
  6. package/cli/index.js +2 -1
  7. package/core/accessors.js +341 -54
  8. package/core/account.js +74 -20
  9. package/core/analysis.js +257 -125
  10. package/core/ast-analysis.js +12 -3
  11. package/core/bridge.js +15 -65
  12. package/core/brief.js +8 -4
  13. package/core/build-worker.js +2 -1
  14. package/core/cache.js +137 -6
  15. package/core/call-name-cache.js +134 -0
  16. package/core/callable-scope.js +21 -0
  17. package/core/callers.js +4726 -940
  18. package/core/check.js +22 -5
  19. package/core/class-identity.js +399 -12
  20. package/core/client-receivers.js +189 -0
  21. package/core/constructor-calls.js +831 -0
  22. package/core/constructor-hierarchy.js +147 -0
  23. package/core/constructor-protocol.js +196 -0
  24. package/core/contract-membership.js +346 -140
  25. package/core/cpp-conversion-constraints.js +123 -0
  26. package/core/cpp-conversion-syntax.js +216 -0
  27. package/core/cpp-conversions.js +414 -0
  28. package/core/cpp-scope.js +245 -44
  29. package/core/deadcode.js +61 -16
  30. package/core/decorator-binding.js +137 -3
  31. package/core/enum-reference-syntax.js +134 -0
  32. package/core/execute.js +17 -1
  33. package/core/external-macros.js +42 -10
  34. package/core/go-packages.js +171 -0
  35. package/core/graph-build.js +2 -0
  36. package/core/graph.js +75 -59
  37. package/core/ignore-rules.js +2 -1
  38. package/core/imports.js +145 -19
  39. package/core/index-ir.js +41 -4
  40. package/core/inprocess-clients.js +41 -15
  41. package/core/ir.js +27 -6
  42. package/core/macro-expansion.js +38 -2
  43. package/core/out-of-class-members.js +182 -0
  44. package/core/output/analysis-ext.js +4 -2
  45. package/core/output/analysis.js +89 -20
  46. package/core/output/graph.js +12 -3
  47. package/core/output/lines.js +4 -3
  48. package/core/output/public.js +2 -0
  49. package/core/output/reporting.js +12 -1
  50. package/core/output/shared.js +2 -0
  51. package/core/output/tracing.js +28 -22
  52. package/core/output-budget.js +2 -2
  53. package/core/parallel-build.js +52 -1
  54. package/core/project.js +217 -112
  55. package/core/provenance-constructor-base.js +161 -0
  56. package/core/provenance-facts.js +257 -27
  57. package/core/provenance-overload.js +458 -54
  58. package/core/provenance-type.js +487 -0
  59. package/core/provenance.js +234 -15
  60. package/core/public-command.js +17 -2
  61. package/core/query-bodies.js +159 -0
  62. package/core/reflection.js +270 -2
  63. package/core/rename-token-syntax.js +111 -0
  64. package/core/reporting.js +13 -7
  65. package/core/request-helpers.js +63 -7
  66. package/core/rust-macro-expansion.js +151 -19
  67. package/core/rust-modules.js +1 -1
  68. package/core/rust-names.js +607 -0
  69. package/core/search.js +70 -17
  70. package/core/shared.js +98 -1
  71. package/core/source-memo.js +201 -0
  72. package/core/text-usage-syntax.js +38 -0
  73. package/core/tracing.js +7 -1
  74. package/core/type-denotation.js +124 -37
  75. package/core/type-references.js +615 -246
  76. package/core/value-references.js +2777 -0
  77. package/core/verify.js +1097 -281
  78. package/languages/adapter.js +28 -4
  79. package/languages/c-family.js +1118 -164
  80. package/languages/constructor-bases.js +105 -0
  81. package/languages/cpp-platform.js +26 -0
  82. package/languages/cpp-site-syntax.js +83 -0
  83. package/languages/csharp-scope.js +439 -0
  84. package/languages/csharp.js +428 -68
  85. package/languages/go.js +385 -207
  86. package/languages/html.js +4 -2
  87. package/languages/index.js +397 -2
  88. package/languages/java-platform.js +226 -0
  89. package/languages/java-unicode.js +269 -0
  90. package/languages/java.js +461 -117
  91. package/languages/javascript.js +418 -66
  92. package/languages/lexical-scope.js +190 -23
  93. package/languages/python.js +404 -54
  94. package/languages/rust.js +510 -82
  95. package/languages/utils.js +67 -8
  96. package/mcp/server.js +8 -2
  97. package/package.json +3 -3
package/languages/java.js CHANGED
@@ -18,6 +18,7 @@ const {
18
18
  visitNameNodes,
19
19
  sameNode,
20
20
  parseErrorRegions,
21
+ hasParameterizedQualifier,
21
22
  } = require('./utils');
22
23
  const { PARSE_OPTIONS, safeParse } = require('./index');
23
24
 
@@ -25,6 +26,50 @@ function parseTree(parser, code) {
25
26
  return safeParse(parser, code, undefined, PARSE_OPTIONS);
26
27
  }
27
28
 
29
+ const ANNOTATION_BEFORE_DOTS = /@\s*[A-Za-z_$][\w$.]*(?:\s*\((?:[^()]|\([^()]*\))*\))?\s*\.\.\./;
30
+ // An annotation (with optional arguments) written right before `...`.
31
+ const VARARGS_ANNOTATION = /@\s*[A-Za-z_$][\w$]*(?:\s*\.\s*[A-Za-z_$][\w$]*)*(?:\s*\((?:[^()]|\([^()]*\))*\))?\s*(?=\.\.\.)/g;
32
+
33
+ /**
34
+ * Recovery of a grammar gap (fix #428): tree-sitter-java does not parse a
35
+ * type annotation between a variable arity parameter's type and its `...`
36
+ * (`Object @Nullable ... args`, JLS 8.4.1), and drops the parameter. Inside
37
+ * each parse error of a parameter list, such annotations are blanked (same
38
+ * offsets) and the source reparsed; the recovered tree is kept only when it
39
+ * has fewer errors. Annotations carry no call or type facts the index reads
40
+ * there.
41
+ */
42
+ function recoverTypeAnnotatedVarargs(tree, source, reparse) {
43
+ // Only a source spelling an annotation right before `...` can hold the
44
+ // gap: one text test before any tree walk.
45
+ if (!source.includes('...') || !ANNOTATION_BEFORE_DOTS.test(source)) return null;
46
+ const ranges = [];
47
+ for (const error of tree.rootNode.descendantsOfType('ERROR')) {
48
+ let inParameters = false;
49
+ for (let node = error.parent; node && !inParameters; node = node.parent) {
50
+ if (node.type === 'formal_parameters') inParameters = true;
51
+ if (node.type === 'class_body' || node.type === 'program') break;
52
+ }
53
+ if (!inParameters) continue;
54
+ const text = source.slice(error.startIndex, error.endIndex);
55
+ for (const match of text.matchAll(VARARGS_ANNOTATION)) {
56
+ ranges.push([error.startIndex + match.index, error.startIndex + match.index + match[0].length]);
57
+ }
58
+ }
59
+ if (ranges.length === 0) return null;
60
+ let blanked = '';
61
+ let at = 0;
62
+ for (const [start, end] of ranges.sort((a, b) => a[0] - b[0])) {
63
+ if (start < at) continue;
64
+ blanked += source.slice(at, start) + source.slice(start, end).replace(/[^\n]/g, ' ');
65
+ at = end;
66
+ }
67
+ blanked += source.slice(at);
68
+ const recovered = reparse(blanked);
69
+ const errors = root => root.descendantsOfType('ERROR').length + (root.hasError ? 1 : 0);
70
+ return recovered && errors(recovered.rootNode) < errors(tree.rootNode) ? recovered : null;
71
+ }
72
+
28
73
  /**
29
74
  * Extract Java parameters
30
75
  */
@@ -35,7 +80,8 @@ function extractJavaParams(paramsNode) {
35
80
  // the shared utils.extractParams already had it).
36
81
  if (!paramsNode) return '...';
37
82
  const text = nodeTextWithoutComments(paramsNode);
38
- let params = text.replace(/^\(|\)$/g, '').trim();
83
+ // A recovered variable arity annotation leaves blanks before `...`.
84
+ let params = text.replace(/^\(|\)$/g, '').trim().replace(/ {2,}(?=\.\.\.)/g, ' ');
39
85
  return params;
40
86
  }
41
87
 
@@ -240,7 +286,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
240
286
  const paramsNode = node.childForFieldName('parameters');
241
287
 
242
288
  if (nameNode) {
243
- const { startLine, endLine, indent } = nodeToLocation(node, lines);
289
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
244
290
  const modifiers = extractModifiers(node);
245
291
  const annotations = extractAnnotations(node);
246
292
  const annotationsWithArgs = extractAnnotationsWithArgs(node);
@@ -256,6 +302,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
256
302
  paramsStructured: parseStructuredParams(paramsNode, 'java'),
257
303
  startLine,
258
304
  endLine,
305
+ sourceSpan,
259
306
  indent,
260
307
  modifiers,
261
308
  ...(returnType && { returnType }),
@@ -285,7 +332,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
285
332
  const paramsNode = node.childForFieldName('parameters');
286
333
 
287
334
  if (nameNode) {
288
- const { startLine, endLine, indent } = nodeToLocation(node, lines);
335
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
289
336
  const modifiers = extractModifiers(node);
290
337
  const annotations = extractAnnotations(node);
291
338
  const annotationsWithArgs = extractAnnotationsWithArgs(node);
@@ -298,6 +345,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
298
345
  paramsStructured: parseStructuredParams(paramsNode, 'java'),
299
346
  startLine,
300
347
  endLine,
348
+ sourceSpan,
301
349
  indent,
302
350
  modifiers,
303
351
  isConstructor: true,
@@ -372,6 +420,65 @@ const JAVA_TYPE_DECLARATIONS = new Set([
372
420
  'class_declaration', 'interface_declaration', 'enum_declaration', 'record_declaration',
373
421
  'annotation_type_declaration',
374
422
  ]);
423
+ // Type declarations that declare constructors.
424
+ const JAVA_CONSTRUCTOR_OWNER_TYPES = new Set(['class_declaration', 'enum_declaration', 'record_declaration']);
425
+
426
+ // A parameter type's erasure, read from its AST: type arguments and
427
+ // annotations dropped, array dimensions kept (`T...` is `T[]`).
428
+ function javaErasedType(typeNode) {
429
+ if (!typeNode) return null;
430
+ switch (typeNode.type) {
431
+ case 'generic_type':
432
+ return javaErasedType(typeNode.namedChildren.find(child =>
433
+ child.type === 'type_identifier' || child.type === 'scoped_type_identifier'));
434
+ case 'annotated_type':
435
+ return javaErasedType(typeNode.namedChildren.find(child =>
436
+ child.type !== 'marker_annotation' && child.type !== 'annotation'));
437
+ case 'array_type': {
438
+ const element = javaErasedType(typeNode.childForFieldName('element'));
439
+ const dimensions = typeNode.childForFieldName('dimensions');
440
+ const count = dimensions ? dimensions.children.filter(child => child.type === '[').length : 1;
441
+ return element && `${element}${'[]'.repeat(count)}`;
442
+ }
443
+ case 'scoped_type_identifier':
444
+ return typeNode.namedChildren.map(javaErasedType).join('.');
445
+ default:
446
+ return typeNode.text;
447
+ }
448
+ }
449
+
450
+ function javaErasedParameters(paramsNode) {
451
+ const erased = [];
452
+ for (const param of paramsNode?.namedChildren || []) {
453
+ if (param.type !== 'formal_parameter' && param.type !== 'spread_parameter') continue;
454
+ const typeNode = param.childForFieldName('type') || param.namedChildren.find(child =>
455
+ child.type !== 'modifiers' && child.type !== 'variable_declarator' && child.type !== 'identifier');
456
+ const type = javaErasedType(typeNode);
457
+ if (!type) return null;
458
+ // `int b[]` writes its dimensions after the name.
459
+ const dimensions = param.childForFieldName('dimensions');
460
+ const trailing = dimensions ? '[]'.repeat(dimensions.children.filter(child => child.type === '[').length) : '';
461
+ erased.push(`${type}${trailing}${param.type === 'spread_parameter' ? '[]' : ''}`);
462
+ }
463
+ return erased;
464
+ }
465
+
466
+ // Does a record's body declare its canonical constructor (JLS 8.10.4)?
467
+ // A compact constructor is canonical; so is a constructor whose parameter
468
+ // types are the component types. An undecidable spelling counts as not
469
+ // canonical: the implicit candidate then shares its signature, which
470
+ // leaves the choice between them open rather than closed.
471
+ function recordDeclaresCanonical(recordNode, paramsNode) {
472
+ const components = javaErasedParameters(paramsNode);
473
+ for (const child of recordNode.childForFieldName('body')?.namedChildren || []) {
474
+ if (child.type === 'compact_constructor_declaration') return true;
475
+ if (child.type !== 'constructor_declaration' || !components) continue;
476
+ const declared = javaErasedParameters(child.childForFieldName('parameters'));
477
+ if (declared && declared.length === components.length &&
478
+ declared.every((type, at) => type === components[at])) return true;
479
+ }
480
+ return false;
481
+ }
375
482
 
376
483
  function _processClass(node, classes, processedRanges, lines, code) {
377
484
  // Only type declarations are processed below: every other node returns
@@ -386,7 +493,7 @@ function _processClass(node, classes, processedRanges, lines, code) {
386
493
 
387
494
  const nameNode = node.childForFieldName('name');
388
495
  if (nameNode) {
389
- const { startLine, endLine } = nodeToLocation(node, lines);
496
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
390
497
  const members = extractClassMembers(node, lines);
391
498
  const modifiers = extractModifiers(node);
392
499
  const annotations = extractAnnotations(node);
@@ -405,6 +512,7 @@ function _processClass(node, classes, processedRanges, lines, code) {
405
512
  name: nameNode.text,
406
513
  startLine,
407
514
  endLine,
515
+ sourceSpan,
408
516
  // The name's own line when annotations precede it (fix #389).
409
517
  ...(nameNode.startPosition.row + 1 !== startLine && { nameLine: nameNode.startPosition.row + 1 }),
410
518
  type: 'class',
@@ -435,7 +543,7 @@ function _processClass(node, classes, processedRanges, lines, code) {
435
543
 
436
544
  const nameNode = node.childForFieldName('name');
437
545
  if (nameNode) {
438
- const { startLine, endLine } = nodeToLocation(node, lines);
546
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
439
547
  const modifiers = extractModifiers(node);
440
548
  const annotations = extractAnnotations(node);
441
549
  const annotationsWithArgs = extractAnnotationsWithArgs(node);
@@ -447,6 +555,7 @@ function _processClass(node, classes, processedRanges, lines, code) {
447
555
  name: nameNode.text,
448
556
  startLine,
449
557
  endLine,
558
+ sourceSpan,
450
559
  // The name's own line when annotations precede it (fix #389).
451
560
  ...(nameNode.startPosition.row + 1 !== startLine && { nameLine: nameNode.startPosition.row + 1 }),
452
561
  type: 'interface',
@@ -473,7 +582,7 @@ function _processClass(node, classes, processedRanges, lines, code) {
473
582
 
474
583
  const nameNode = node.childForFieldName('name');
475
584
  if (nameNode) {
476
- const { startLine, endLine } = nodeToLocation(node, lines);
585
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
477
586
  const modifiers = extractModifiers(node);
478
587
  const annotations = extractAnnotations(node);
479
588
  const annotationsWithArgs = extractAnnotationsWithArgs(node);
@@ -486,6 +595,7 @@ function _processClass(node, classes, processedRanges, lines, code) {
486
595
  name: nameNode.text,
487
596
  startLine,
488
597
  endLine,
598
+ sourceSpan,
489
599
  // The name's own line when annotations precede it (fix #389).
490
600
  ...(nameNode.startPosition.row + 1 !== startLine && { nameLine: nameNode.startPosition.row + 1 }),
491
601
  type: 'enum',
@@ -510,7 +620,7 @@ function _processClass(node, classes, processedRanges, lines, code) {
510
620
 
511
621
  const nameNode = node.childForFieldName('name');
512
622
  if (nameNode) {
513
- const { startLine, endLine } = nodeToLocation(node, lines);
623
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
514
624
  const modifiers = extractModifiers(node);
515
625
  const annotations = extractAnnotations(node);
516
626
  const annotationsWithArgs = extractAnnotationsWithArgs(node);
@@ -546,11 +656,18 @@ function _processClass(node, classes, processedRanges, lines, code) {
546
656
  name: nameNode.text,
547
657
  startLine,
548
658
  endLine,
659
+ sourceSpan,
549
660
  // The name's own line when annotations precede it (fix #389).
550
661
  ...(nameNode.startPosition.row + 1 !== startLine && { nameLine: nameNode.startPosition.row + 1 }),
551
662
  type: 'record',
552
663
  members,
553
664
  modifiers,
665
+ // The implicit canonical constructor: a constructor the
666
+ // header declares with no body declaration, like a C#
667
+ // primary constructor (fix #419).
668
+ ...(paramsNode && !recordDeclaresCanonical(node, paramsNode) && {
669
+ primaryParams: parseStructuredParams(paramsNode, 'java'),
670
+ }),
554
671
  ...localScope,
555
672
  ...(enclosingTypeName(node) && { enclosingType: enclosingTypeName(node) }),
556
673
  ...(docstring && { docstring }),
@@ -672,12 +789,13 @@ function extractEnumConstants(enumNode, codeOrLines) {
672
789
  if (child.type === 'enum_constant') {
673
790
  const nameNode = child.childForFieldName('name');
674
791
  if (nameNode) {
675
- const { startLine, endLine } = nodeToLocation(child, code);
792
+ const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
676
793
  const argsNode = child.childForFieldName('arguments');
677
794
  constants.push({
678
795
  name: nameNode.text,
679
796
  startLine,
680
797
  endLine,
798
+ sourceSpan,
681
799
  memberType: 'constant',
682
800
  // JLS: enum constants are implicitly public static final
683
801
  // (fix #251 — api omitted them for lack of a modifier).
@@ -699,7 +817,7 @@ function extractEnumConstants(enumNode, codeOrLines) {
699
817
  const nameNode = member.childForFieldName('name');
700
818
  const paramsNode = member.childForFieldName('parameters');
701
819
  if (nameNode) {
702
- const { startLine, endLine } = nodeToLocation(member, code);
820
+ const { startLine, endLine, sourceSpan } = nodeToLocation(member, code);
703
821
  const modifiers = extractModifiers(member);
704
822
  const returnType = extractReturnType(member);
705
823
  // The name's own line and annotations, as for class
@@ -714,6 +832,7 @@ function extractEnumConstants(enumNode, codeOrLines) {
714
832
  paramsStructured: parseStructuredParams(paramsNode, 'java'),
715
833
  startLine,
716
834
  endLine,
835
+ sourceSpan,
717
836
  memberType: modifiers.includes('static') ? 'static' : 'method',
718
837
  modifiers,
719
838
  isMethod: true,
@@ -723,11 +842,14 @@ function extractEnumConstants(enumNode, codeOrLines) {
723
842
  ...(memberGenerics && { generics: memberGenerics })
724
843
  });
725
844
  }
845
+ } else if (member.type === 'field_declaration') {
846
+ // Fields after the constants (fix #430).
847
+ constants.push(...javaFieldMembers(member, code));
726
848
  } else if (member.type === 'constructor_declaration') {
727
849
  const nameNode = member.childForFieldName('name');
728
850
  const paramsNode = member.childForFieldName('parameters');
729
851
  if (nameNode) {
730
- const { startLine, endLine } = nodeToLocation(member, code);
852
+ const { startLine, endLine, sourceSpan } = nodeToLocation(member, code);
731
853
  const modifiers = extractModifiers(member);
732
854
  constants.push({
733
855
  name: nameNode.text,
@@ -738,7 +860,9 @@ function extractEnumConstants(enumNode, codeOrLines) {
738
860
  paramsStructured: parseStructuredParams(paramsNode, 'java'),
739
861
  startLine,
740
862
  endLine,
863
+ sourceSpan,
741
864
  memberType: 'constructor',
865
+ isConstructor: true,
742
866
  modifiers,
743
867
  isMethod: true
744
868
  });
@@ -752,6 +876,48 @@ function extractEnumConstants(enumNode, codeOrLines) {
752
876
  return constants;
753
877
  }
754
878
 
879
+ /**
880
+ * The field members a field or constant declaration declares. Interface and
881
+ * annotation type constants (`constant_declaration`, implicitly public static
882
+ * final; fix #389) are fields like class fields.
883
+ */
884
+ function javaFieldMembers(child, code) {
885
+ const members = [];
886
+ const typeNode = child.childForFieldName('type');
887
+ const fieldTypeText = typeNode ? typeNode.text : null;
888
+ // Visibility travels with the member (fix #251 — public
889
+ // instance fields were invisible to api/fileExports because
890
+ // the member had no modifiers for the #240 discipline to read;
891
+ // the #241 Rust-field twin).
892
+ const fieldModifiers = extractModifiers(child);
893
+ if (child.type === 'constant_declaration') {
894
+ for (const implicit of ['public', 'static', 'final']) {
895
+ if (!fieldModifiers.includes(implicit)) fieldModifiers.push(implicit);
896
+ }
897
+ }
898
+ for (let j = 0; j < child.namedChildCount; j++) {
899
+ const decl = child.namedChild(j);
900
+ if (decl.type !== 'variable_declarator') continue;
901
+ const nameNode = decl.childForFieldName('name');
902
+ if (!nameNode || !fieldTypeText) continue;
903
+ const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
904
+ // fix #390: an annotation on its own line starts the
905
+ // declaration; the name's line is where edits go.
906
+ const nameLine = nameNode.startPosition.row + 1;
907
+ members.push({
908
+ name: nameNode.text,
909
+ startLine,
910
+ endLine,
911
+ sourceSpan,
912
+ ...(nameLine !== startLine && { nameLine }),
913
+ memberType: 'field',
914
+ ...(fieldModifiers.length > 0 && { modifiers: [...fieldModifiers] }),
915
+ fieldType: fieldTypeText
916
+ });
917
+ }
918
+ return members;
919
+ }
920
+
755
921
  /**
756
922
  * Extract class members (methods, constructors)
757
923
  */
@@ -774,7 +940,7 @@ function extractClassMembers(classNode, codeOrLines) {
774
940
  const paramsNode = child.childForFieldName('parameters');
775
941
 
776
942
  if (nameNode) {
777
- const { startLine, endLine } = nodeToLocation(child, code);
943
+ const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
778
944
  const modifiers = extractModifiers(child);
779
945
  const annotationsWithArgs = extractAnnotationsWithArgs(child);
780
946
  // Interface methods are implicitly public and abstract in Java
@@ -802,6 +968,7 @@ function extractClassMembers(classNode, codeOrLines) {
802
968
  paramsStructured: parseStructuredParams(paramsNode, 'java'),
803
969
  startLine,
804
970
  endLine,
971
+ sourceSpan,
805
972
  memberType,
806
973
  modifiers,
807
974
  isMethod: true, // Mark as method for context() lookups
@@ -822,7 +989,7 @@ function extractClassMembers(classNode, codeOrLines) {
822
989
  if (child.type === 'annotation_type_element_declaration') {
823
990
  const nameNode = child.childForFieldName('name');
824
991
  if (nameNode) {
825
- const { startLine, endLine } = nodeToLocation(child, code);
992
+ const { startLine, endLine, sourceSpan } = nodeToLocation(child, code);
826
993
  // Elements take no keyword modifiers besides public/abstract;
827
994
  // `default` here introduces the default value.
828
995
  const modifiers = extractModifiers(child).filter(modifier =>
@@ -838,6 +1005,7 @@ function extractClassMembers(classNode, codeOrLines) {
838
1005
  paramsStructured: [],
839
1006
  startLine,
840
1007
  endLine,
1008
+ sourceSpan,
841
1009
  memberType: 'abstract',
842
1010
  modifiers,
843
1011
  isMethod: true,
@@ -848,53 +1016,36 @@ function extractClassMembers(classNode, codeOrLines) {
848
1016
  }
849
1017
  }
850
1018
 
851
- // Constructor declarations: intentionally NOT emitted as separate class
852
- // members. The class itself is the symbol; `new Foo(...)` calls resolve
853
- // to the class via `isConstructor: true` on the call. Emitting the
854
- // constructor as a member would create duplicate `find Foo` results
855
- // (one for class, one for constructor), forcing users to disambiguate.
856
- // Constructor signature info (params, line) remains accessible by reading
857
- // the class body when needed (e.g. via verify's AST walk).
1019
+ if (child.type === 'constructor_declaration' || child.type === 'compact_constructor_declaration') {
1020
+ const nameNode = child.childForFieldName('name');
1021
+ const paramsNode = child.childForFieldName('parameters') ||
1022
+ (child.type === 'compact_constructor_declaration' && classNode.childForFieldName('parameters'));
1023
+ if (nameNode) {
1024
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(child, code);
1025
+ const nameLine = nameNode.startPosition.row + 1;
1026
+ const annotationsWithArgs = extractAnnotationsWithArgs(child);
1027
+ const generics = extractGenerics(child);
1028
+ members.push({
1029
+ name: nameNode.text,
1030
+ params: extractJavaParams(paramsNode),
1031
+ paramsStructured: parseStructuredParams(paramsNode, 'java'),
1032
+ startLine, endLine, sourceSpan, indent,
1033
+ memberType: 'constructor',
1034
+ isMethod: true,
1035
+ isConstructor: true,
1036
+ modifiers: extractModifiers(child),
1037
+ ...(nameLine !== startLine && { nameLine }),
1038
+ ...(annotationsWithArgs.length > 0 && { annotationsWithArgs }),
1039
+ ...(generics && { generics }),
1040
+ docstring: extractJavaDocstring(code, startLine),
1041
+ });
1042
+ }
1043
+ }
858
1044
 
859
1045
  // Field declarations: declared types drive receiver disambiguation
860
1046
  // (fix #202) — Rust/Go already emit field members with fieldType.
861
- // Interface and annotation type constants (`constant_declaration`,
862
- // implicitly public static final; fix #389) are fields like class
863
- // fields.
864
1047
  if (child.type === 'field_declaration' || child.type === 'constant_declaration') {
865
- const typeNode = child.childForFieldName('type');
866
- const fieldTypeText = typeNode ? typeNode.text : null;
867
- // Visibility travels with the member (fix #251 — public
868
- // instance fields were invisible to api/fileExports because
869
- // the member had no modifiers for the #240 discipline to read;
870
- // the #241 Rust-field twin).
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
- }
877
- for (let j = 0; j < child.namedChildCount; j++) {
878
- const decl = child.namedChild(j);
879
- if (decl.type === 'variable_declarator') {
880
- const nameNode = decl.childForFieldName('name');
881
- if (nameNode && fieldTypeText) {
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;
886
- members.push({
887
- name: nameNode.text,
888
- startLine,
889
- endLine,
890
- ...(nameLine !== startLine && { nameLine }),
891
- memberType: 'field',
892
- ...(fieldModifiers.length > 0 && { modifiers: fieldModifiers }),
893
- fieldType: fieldTypeText
894
- });
895
- }
896
- }
897
- }
1048
+ members.push(...javaFieldMembers(child, code));
898
1049
  }
899
1050
  }
900
1051
 
@@ -920,8 +1071,8 @@ function _processState(node, objects, lines, code) {
920
1071
  if (nameNode && valueNode) {
921
1072
  const name = nameNode.text;
922
1073
  if (_statePattern.test(name)) {
923
- const { startLine, endLine } = nodeToLocation(node, lines);
924
- objects.push({ name, startLine, endLine, modifiers });
1074
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
1075
+ objects.push({ name, startLine, endLine, sourceSpan, modifiers });
925
1076
  }
926
1077
  }
927
1078
  }
@@ -993,6 +1144,7 @@ function parse(code, parser) {
993
1144
  classes,
994
1145
  stateObjects,
995
1146
  ...(tree.rootNode.hasError && { parseRecovery: true, parseErrorRegions: parseErrorRegions(tree.rootNode) }),
1147
+ typeParameterNames: require('./utils').declaredTypeParameterNames(tree),
996
1148
  imports: [],
997
1149
  exports: []
998
1150
  };
@@ -1099,7 +1251,7 @@ function findCallsInCode(code, parser) {
1099
1251
 
1100
1252
  // Helper to check if a node creates a function scope
1101
1253
  const isFunctionNode = (node) => {
1102
- return ['method_declaration', 'constructor_declaration', 'lambda_expression'].includes(node.type);
1254
+ return ['method_declaration', 'constructor_declaration', 'compact_constructor_declaration', 'lambda_expression'].includes(node.type);
1103
1255
  };
1104
1256
  const JAVA_TYPE_DECLARATIONS = new Set([
1105
1257
  'class_declaration', 'interface_declaration',
@@ -1185,6 +1337,52 @@ function findCallsInCode(code, parser) {
1185
1337
  };
1186
1338
  const extractReceiverTypeQualifier = (typeNode) =>
1187
1339
  extractTypeQualifier(typeNode) || extractTypePackage(typeNode);
1340
+ // Pattern variables (fix #425, the Go type-switch rule): `x instanceof
1341
+ // Sq s`, `case Sq s ->` and record components `case Pt(Sq a, ..)` bind a
1342
+ // local of the written type wherever its scope reaches (JLS 6.3.1,
1343
+ // decided by patternBindingReach). Collected once per file by name.
1344
+ let patternBindings = null;
1345
+ // Patterns occur only after `instanceof` and in `case` labels.
1346
+ const mayHavePatterns = /\binstanceof\b|\bcase\b/.test(code);
1347
+ const patternTypeAt = (refNode) => {
1348
+ if (!mayHavePatterns) return null;
1349
+ if (!patternBindings) {
1350
+ patternBindings = new Map();
1351
+ for (const n of tree.rootNode.descendantsOfType(['type_pattern', 'instanceof_expression',
1352
+ 'record_pattern_component'])) {
1353
+ let nameNode;
1354
+ let typeNode;
1355
+ if (n.type === 'instanceof_expression') {
1356
+ nameNode = n.childForFieldName('name');
1357
+ typeNode = n.childForFieldName('right');
1358
+ } else {
1359
+ const kids = n.namedChildren.filter(kid => !kid.type.includes('comment'));
1360
+ nameNode = kids.length === 2 ? kids[1] : null;
1361
+ typeNode = kids.length === 2 ? kids[0] : null;
1362
+ }
1363
+ if (nameNode?.type !== 'identifier' || !typeNode || typeNode.text === 'var') continue;
1364
+ if (!patternBindings.has(nameNode.text)) patternBindings.set(nameNode.text, []);
1365
+ patternBindings.get(nameNode.text).push({ nameNode, typeNode });
1366
+ }
1367
+ }
1368
+ const list = refNode?.type === 'identifier' ? patternBindings.get(refNode.text) : null;
1369
+ if (!list) return null;
1370
+ for (let i = list.length - 1; i >= 0; i--) {
1371
+ const { nameNode, typeNode } = list[i];
1372
+ if (patternBindingReach(nameNode, refNode) !== 'in') continue;
1373
+ const type = extractTypeName(typeNode);
1374
+ if (!type) return null;
1375
+ return {
1376
+ type,
1377
+ qualifier: extractReceiverTypeQualifier(typeNode) || undefined,
1378
+ evidence: {
1379
+ receiverTypeSource: 'annotation',
1380
+ receiverTypeEvidence: { ...typeOrigin('annotation', typeNode), name: refNode.text, type },
1381
+ },
1382
+ };
1383
+ }
1384
+ return null;
1385
+ };
1188
1386
  const qualifyTypeName = (text) => {
1189
1387
  const raw = String(text || '').trim()
1190
1388
  .replace(/^\?\s+extends\s+/, '')
@@ -1193,7 +1391,9 @@ function findCallsInCode(code, parser) {
1193
1391
  if (!base) return null;
1194
1392
  if (base.includes('.')) return base;
1195
1393
  if (importedTypeNames.has(base)) return importedTypeNames.get(base);
1196
- if (['Class', 'Enum', 'Object', 'String', 'Throwable'].includes(base)) {
1394
+ // String and Class can name project types. Preserve those spellings
1395
+ // for query-time lookup in the file and lexical owner that wrote them.
1396
+ if (['Enum', 'Object', 'Throwable'].includes(base)) {
1197
1397
  return `java.lang.${base}`;
1198
1398
  }
1199
1399
  return base;
@@ -1338,7 +1538,7 @@ function findCallsInCode(code, parser) {
1338
1538
  const nameNode = node.childForFieldName('name');
1339
1539
  return nameNode?.text || '<anonymous>';
1340
1540
  }
1341
- if (node.type === 'constructor_declaration') {
1541
+ if (node.type === 'constructor_declaration' || node.type === 'compact_constructor_declaration') {
1342
1542
  const nameNode = node.childForFieldName('name');
1343
1543
  return nameNode?.text || '<constructor>';
1344
1544
  }
@@ -1519,7 +1719,8 @@ function findCallsInCode(code, parser) {
1519
1719
  let ownerNode = node.childForFieldName('object');
1520
1720
  if (!ownerNode) {
1521
1721
  const owner = findEnclosingClassName(node);
1522
- return owner ? { owner, methods: [method] } : null;
1722
+ return owner ? { owner, written: owner, site: typeOrigin('call', node),
1723
+ arities: [getCallArgCount(node)], methods: [method] } : null;
1523
1724
  }
1524
1725
  while (ownerNode?.type === 'parenthesized_expression' &&
1525
1726
  ownerNode.namedChildCount === 1) {
@@ -1528,7 +1729,8 @@ function findCallsInCode(code, parser) {
1528
1729
  if (ownerNode?.type === 'method_invocation') {
1529
1730
  const parent = methodCallPathOf(ownerNode);
1530
1731
  return parent
1531
- ? { owner: parent.owner, methods: [...parent.methods, method] }
1732
+ ? { ...parent, methods: [...parent.methods, method],
1733
+ arities: [...parent.arities, getCallArgCount(node)] }
1532
1734
  : null;
1533
1735
  }
1534
1736
  let ownerType = null;
@@ -1542,7 +1744,8 @@ function findCallsInCode(code, parser) {
1542
1744
  ownerType = ownerNode.childForFieldName('type')?.text;
1543
1745
  }
1544
1746
  return ownerType
1545
- ? { owner: qualifyTypeName(ownerType), methods: [method] }
1747
+ ? { owner: qualifyTypeName(ownerType), written: ownerType,
1748
+ site: typeOrigin('call', node), arities: [getCallArgCount(node)], methods: [method] }
1546
1749
  : null;
1547
1750
  };
1548
1751
  const argKindOf = (arg) => {
@@ -1631,7 +1834,7 @@ function findCallsInCode(code, parser) {
1631
1834
  if (!methodNode) return 'expr';
1632
1835
  if (!ownerNode) {
1633
1836
  const owner = findEnclosingClassName(arg);
1634
- return owner ? `call:${owner}:${methodNode.text}` : 'expr';
1837
+ return owner ? methodResultKind(owner, methodNode.text, arg) : 'expr';
1635
1838
  }
1636
1839
  let normalizedOwner = ownerNode;
1637
1840
  while (normalizedOwner?.type === 'parenthesized_expression' &&
@@ -1642,7 +1845,9 @@ function findCallsInCode(code, parser) {
1642
1845
  let ownerRawType = null;
1643
1846
  if (normalizedOwner?.type === 'identifier') {
1644
1847
  ownerType = getReceiverType(normalizedOwner.text) ||
1645
- (/^[A-Z]/.test(normalizedOwner.text) ? normalizedOwner.text : null);
1848
+ (!isDeclaredLocal(normalizedOwner.text) &&
1849
+ !hasEnclosingField(arg, normalizedOwner.text) &&
1850
+ /^[A-Z]/.test(normalizedOwner.text) ? normalizedOwner.text : null);
1646
1851
  ownerRawType = getReceiverRawType(normalizedOwner.text);
1647
1852
  } else if (normalizedOwner?.type === 'cast_expression') {
1648
1853
  const typeNode = normalizedOwner.childForFieldName('type');
@@ -1670,40 +1875,19 @@ function findCallsInCode(code, parser) {
1670
1875
  }
1671
1876
  }
1672
1877
  }
1673
- // Generic container access has a source-declared result type:
1674
- // List<T>.get(i) -> T, Map<K,V>.get(k) -> V. Wildcard
1675
- // `? extends T` is safe in value position; `? super T` is not.
1676
- if (methodNode.text === 'get' && ownerRawType?.includes('<')) {
1677
- const generic = ownerRawType.slice(
1678
- ownerRawType.indexOf('<') + 1,
1679
- ownerRawType.lastIndexOf('>'));
1680
- const args = [];
1681
- let start = 0;
1682
- let depth = 0;
1683
- for (let i = 0; i <= generic.length; i++) {
1684
- const ch = generic[i];
1685
- if (ch === '<') depth++;
1686
- else if (ch === '>') depth--;
1687
- else if ((ch === ',' || i === generic.length) && depth === 0) {
1688
- args.push(generic.slice(start, i).trim());
1689
- start = i + 1;
1690
- }
1691
- }
1692
- const base = bareTypeName(ownerRawType);
1693
- const picked = base === 'Map' ? args[1] : args[0];
1694
- if (picked && !/^\?\s+super\b/.test(picked)) {
1695
- const valueType = picked.replace(/^\?\s+extends\s+/, '').trim();
1696
- if (valueType && valueType !== '?') {
1697
- return `type:${qualifyTypeName(valueType)}`;
1698
- }
1699
- }
1700
- }
1878
+ // Generic get() return contracts are resolved from the
1879
+ // receiver's actual type at query time. A project container
1880
+ // does not acquire List/Map semantics from its type arguments.
1701
1881
  if (ownerType) {
1702
- return `call:${qualifyTypeName(ownerRawType || ownerType)}:${methodNode.text}`;
1882
+ const written = ownerRawType || ownerType;
1883
+ return methodResultKind(hasParameterizedQualifier(written)
1884
+ ? written : qualifyTypeName(written), methodNode.text, arg, written);
1703
1885
  }
1704
1886
  const path = methodCallPathOf(arg);
1705
1887
  if (path?.methods.length > 1) {
1706
- return `chain:${path.owner}#${path.methods.join('#')}`;
1888
+ return `chain:${path.owner}#${path.methods.join('#')}|` +
1889
+ encodeURIComponent(JSON.stringify({ written: path.written, site: path.site,
1890
+ arities: path.arities }));
1707
1891
  }
1708
1892
  return 'expr';
1709
1893
  }
@@ -1749,6 +1933,17 @@ function findCallsInCode(code, parser) {
1749
1933
  default: return 'expr';
1750
1934
  }
1751
1935
  };
1936
+ // The argument count alone: a chain records each hop's arity without
1937
+ // deriving every nested argument's kind again (fix #418).
1938
+ const getCallArgCount = (callNode) => {
1939
+ const argsNode = callNode.childForFieldName('arguments');
1940
+ if (!argsNode) return 0;
1941
+ let count = 0;
1942
+ for (let i = 0; i < argsNode.namedChildCount; i++) {
1943
+ if (!argsNode.namedChild(i).type.endsWith('comment')) count++;
1944
+ }
1945
+ return count;
1946
+ };
1752
1947
  const getCallArgs = (callNode) => {
1753
1948
  const argsNode = callNode.childForFieldName('arguments');
1754
1949
  if (!argsNode) return { argCount: 0, argKinds: null };
@@ -1760,6 +1955,13 @@ function findCallsInCode(code, parser) {
1760
1955
  }
1761
1956
  return { argCount: kinds.length, argKinds: kinds.some(k => k !== 'expr') ? kinds : null };
1762
1957
  };
1958
+ const methodResultKind = (owner, method, node, written = owner) => {
1959
+ const args = getCallArgs(node);
1960
+ const kinds = args.argKinds || Array(args.argCount).fill('expr');
1961
+ return `callshape:${encodeURIComponent(owner)}|${encodeURIComponent(method)}|` +
1962
+ kinds.map(encodeURIComponent).join(',') + '|' +
1963
+ encodeURIComponent(JSON.stringify({ written, site: typeOrigin('call', node) }));
1964
+ };
1763
1965
 
1764
1966
  traverseTree(tree.rootNode, (node) => {
1765
1967
  // Track function entry
@@ -1878,10 +2080,13 @@ function findCallsInCode(code, parser) {
1878
2080
  }
1879
2081
  }
1880
2082
  }
2083
+ const pattern = !castReceiverType && receiverNode?.type === 'identifier'
2084
+ ? patternTypeAt(receiverNode) : null;
1881
2085
  const receiverType = castReceiverType || inlineConstructorType || arrayElementType ||
1882
- ((receiver && receiver !== 'this') ? getReceiverType(receiver) : undefined);
2086
+ pattern?.type || ((receiver && receiver !== 'this') ? getReceiverType(receiver) : undefined);
1883
2087
  const receiverTypeQualifier = packageQualifier || arrayElementQualifier ||
1884
- (!castReceiverType && receiver ? getReceiverTypeQualifier(receiver) : undefined);
2088
+ (pattern ? pattern.qualifier : undefined) ||
2089
+ (!castReceiverType && !pattern && receiver ? getReceiverTypeQualifier(receiver) : undefined);
1885
2090
  const receiverIsTypeQualified = !!((receiverNode?.type === 'identifier' || packageQualifier) &&
1886
2091
  receiver && /^[A-Z]/.test(receiver) && !receiverType &&
1887
2092
  !isDeclaredLocal(receiver) && !hasEnclosingField(node, receiver));
@@ -1949,8 +2154,11 @@ function findCallsInCode(code, parser) {
1949
2154
  ? methodCallPathOf(objNode) : null;
1950
2155
  const receiverCallTypePath = receiverPath
1951
2156
  ? (receiverPath.methods.length === 1
1952
- ? `call:${receiverPath.owner}:${receiverPath.methods[0]}`
1953
- : `chain:${receiverPath.owner}#${receiverPath.methods.join('#')}`)
2157
+ ? methodResultKind(receiverPath.owner, receiverPath.methods[0], objNode,
2158
+ receiverPath.written)
2159
+ : `chain:${receiverPath.owner}#${receiverPath.methods.join('#')}|` +
2160
+ encodeURIComponent(JSON.stringify({ written: receiverPath.written,
2161
+ site: receiverPath.site, arities: receiverPath.arities })))
1954
2162
  : null;
1955
2163
  calls.push({
1956
2164
  callSite: typeOrigin('call', nameNode),
@@ -1961,7 +2169,8 @@ function findCallsInCode(code, parser) {
1961
2169
  line: nameNode.startPosition.row + 1,
1962
2170
  isMethod: !!objNode,
1963
2171
  receiver,
1964
- ...(receiverType && { receiverType, ...(getReceiverType(receiver, true) ||
2172
+ ...(receiverType && { receiverType, ...((pattern && pattern.type === receiverType && pattern.evidence) ||
2173
+ getReceiverType(receiver, true) ||
1965
2174
  (castReceiverType ? { receiverTypeSource: 'cast', receiverTypeEvidence: typeOrigin('cast', objNode) } :
1966
2175
  inlineConstructorType ? { receiverTypeSource: 'constructor', receiverTypeEvidence: typeOrigin('constructor', receiverNode) } :
1967
2176
  arrayElementType ? { receiverTypeSource: 'annotation', receiverTypeEvidence: typeOrigin('annotation', receiverNode) } :
@@ -2002,11 +2211,14 @@ function findCallsInCode(code, parser) {
2002
2211
  // super(x) / this(x) — constructor delegation (fix #238: these
2003
2212
  // sites were invisible to every command). Resolve to the parent
2004
2213
  // class's constructor (super) or a same-class overload (this);
2005
- // Java constructors are indexed under the CLASS name, so the
2006
- // record carries the target class as its name.
2214
+ // Java constructors are named after their class. The record carries
2215
+ // that name, and constructor resolution selects its overload.
2007
2216
  if (node.type === 'explicit_constructor_invocation') {
2217
+ // The constructor's own type declaration: a class, enum or
2218
+ // record (a record's non-canonical constructor delegates with
2219
+ // `this(...)`, fix #419).
2008
2220
  let cls = node.parent;
2009
- while (cls && cls.type !== 'class_declaration' && cls.type !== 'enum_declaration') {
2221
+ while (cls && !JAVA_CONSTRUCTOR_OWNER_TYPES.has(cls.type)) {
2010
2222
  cls = cls.parent;
2011
2223
  }
2012
2224
  const isSuperCall = node.children.some(c => c.type === 'super');
@@ -2023,6 +2235,7 @@ function findCallsInCode(code, parser) {
2023
2235
  const genericIdx = targetClass.indexOf('<');
2024
2236
  if (genericIdx > 0) targetClass = targetClass.substring(0, genericIdx);
2025
2237
  const dotIdx = targetClass.lastIndexOf('.');
2238
+ const constructorQualifier = dotIdx > 0 ? targetClass.substring(0, dotIdx) : null;
2026
2239
  if (dotIdx > 0) targetClass = targetClass.substring(dotIdx + 1);
2027
2240
  const enclosingFunction = getCurrentEnclosingFunction();
2028
2241
  const ctorArgs = getCallArgs(node);
@@ -2032,10 +2245,12 @@ function findCallsInCode(code, parser) {
2032
2245
  line: node.startPosition.row + 1,
2033
2246
  isMethod: false,
2034
2247
  isConstructor: true,
2248
+ ...(constructorQualifier && { constructorQualifier }),
2035
2249
  // 'this' delegation names the ENCLOSING class by
2036
2250
  // construction — an intra-class mechanism, never a
2037
2251
  // caller edge for the class (jdtls-measured, fix #238).
2038
2252
  ctorDelegation: isSuperCall ? 'super' : 'this',
2253
+ constructorSyntax: 'delegation',
2039
2254
  argCount: ctorArgs.argCount,
2040
2255
  ...(ctorArgs.argKinds && { argKinds: ctorArgs.argKinds }),
2041
2256
  enclosingFunction
@@ -2067,6 +2282,7 @@ function findCallsInCode(code, parser) {
2067
2282
  // constructor alive for deadcode/--unused, but never a
2068
2283
  // caller edge for the enum (jdtls-measured, fix #238).
2069
2284
  enumConstant: true,
2285
+ constructorSyntax: 'enum',
2070
2286
  argCount: ctorArgs.argCount,
2071
2287
  ...(ctorArgs.argKinds && { argKinds: ctorArgs.argKinds }),
2072
2288
  enclosingFunction: getCurrentEnclosingFunction()
@@ -2088,8 +2304,10 @@ function findCallsInCode(code, parser) {
2088
2304
  // as receiver (fix #206): a qualified type must not resolve to
2089
2305
  // a same-file binding of an unrelated same-name symbol.
2090
2306
  let typeQualifier = null;
2307
+ let constructorQualifier = null;
2091
2308
  const dotIdx = typeName.lastIndexOf('.');
2092
2309
  if (dotIdx > 0) {
2310
+ constructorQualifier = typeName.substring(0, dotIdx);
2093
2311
  const qualParts = typeName.substring(0, dotIdx).split('.');
2094
2312
  typeQualifier = qualParts[qualParts.length - 1] || null;
2095
2313
  typeName = typeName.substring(dotIdx + 1);
@@ -2104,6 +2322,7 @@ function findCallsInCode(code, parser) {
2104
2322
  isMethod: false,
2105
2323
  isConstructor: true,
2106
2324
  ...(typeQualifier && { receiver: typeQualifier }),
2325
+ ...(constructorQualifier && { constructorQualifier }),
2107
2326
  argCount: ctorArgs.argCount,
2108
2327
  ...(ctorArgs.argKinds && { argKinds: ctorArgs.argKinds }),
2109
2328
  enclosingFunction
@@ -2232,24 +2451,27 @@ function findCallsInCode(code, parser) {
2232
2451
  const resTypeNode = node.childForFieldName('type');
2233
2452
  const resNameNode = node.childForFieldName('name');
2234
2453
  const resValueNode = node.childForFieldName('value');
2235
- let typeName = resValueNode?.type === 'object_creation_expression'
2236
- ? extractTypeName(resValueNode.childForFieldName('type'))
2237
- : null;
2238
- let typeQualifier = resValueNode?.type === 'object_creation_expression'
2239
- ? extractReceiverTypeQualifier(resValueNode.childForFieldName('type'))
2240
- : null;
2241
- if (!typeName && resTypeNode && resTypeNode.text !== 'var') {
2242
- typeName = extractTypeName(resTypeNode);
2243
- typeQualifier = extractReceiverTypeQualifier(resTypeNode);
2244
- }
2454
+ // The declared type is the variable's static type; only `var`
2455
+ // takes the constructed one (fix #430: `try (ObjectPool<..> pool =
2456
+ // new TestObjectPool())` calls ObjectPool's members).
2457
+ const declared = resTypeNode && resTypeNode.text !== 'var';
2458
+ const constructed = resValueNode?.type === 'object_creation_expression'
2459
+ ? resValueNode.childForFieldName('type') : null;
2460
+ const typeSource = declared ? resTypeNode : constructed;
2461
+ const typeName = typeSource ? extractTypeName(typeSource) : null;
2462
+ const typeQualifier = typeSource ? extractReceiverTypeQualifier(typeSource) : null;
2463
+ // A resource is final: it holds exactly the constructed value,
2464
+ // as a never-reassigned local does (fix #394).
2465
+ const constructedType = declared && constructed ? extractTypeName(constructed) : null;
2466
+ const exactValue = constructedType && (constructedType !== typeName ||
2467
+ (extractReceiverTypeQualifier(constructed) || null) !== (typeQualifier || null));
2245
2468
  if (resNameNode && typeName) {
2246
2469
  const scopeKey = functionStack[functionStack.length - 1].startLine;
2247
2470
  const typeMap = scopeTypes.get(scopeKey);
2248
- if (typeMap) typeMap.set(resNameNode.text, typeName, 'with-binding', node);
2471
+ if (typeMap) typeMap.set(resNameNode.text, typeName,
2472
+ exactValue ? { ...typeOrigin('with-binding', node), constructedType } : 'with-binding', node);
2249
2473
  const rawTypeMap = scopeRawTypes.get(scopeKey);
2250
- const rawType = resValueNode?.type === 'object_creation_expression'
2251
- ? resValueNode.childForFieldName('type')?.text
2252
- : resTypeNode?.text;
2474
+ const rawType = typeSource?.text;
2253
2475
  if (rawType) rawTypeMap?.set(resNameNode.text, rawType);
2254
2476
  const qualifierMap = scopeTypeQualifiers.get(scopeKey);
2255
2477
  if (typeQualifier) qualifierMap?.set(resNameNode.text, typeQualifier);
@@ -2284,6 +2506,7 @@ function findCallsInCode(code, parser) {
2284
2506
  }
2285
2507
  });
2286
2508
 
2509
+ calls.push(...require('./constructor-bases').implicitBaseCalls(tree.rootNode, 'java'));
2287
2510
  return calls;
2288
2511
  }
2289
2512
 
@@ -2421,6 +2644,116 @@ function findExportsInCode(code, parser) {
2421
2644
  * @param {object} [tree] - Pre-parsed tree (per-operation cache); parsed here when absent
2422
2645
  * @returns {Array<{line: number, column: number, usageType: string}>}
2423
2646
  */
2647
+ function staticValueImportSyntax(tree) {
2648
+ const imports = [];
2649
+ for (const declaration of tree.rootNode.namedChildren) {
2650
+ if (declaration.type !== 'import_declaration' ||
2651
+ !declaration.children.some(child => child.type === 'static')) continue;
2652
+ const path = declaration.namedChildren.find(child =>
2653
+ child.type === 'scoped_identifier' || child.type === 'identifier');
2654
+ if (!path) continue;
2655
+ const wildcard = declaration.children.some(child => child.text === '*');
2656
+ const member = path.childForFieldName('name');
2657
+ const owner = wildcard ? path : path.childForFieldName('scope');
2658
+ if (owner) imports.push({ member: wildcard ? '*' : member?.text,
2659
+ type: owner, token: wildcard ? null : member });
2660
+ }
2661
+ return imports;
2662
+ }
2663
+
2664
+ /**
2665
+ * Whether a pattern variable's scope (JLS 6.3.1) holds a reference (fix
2666
+ * #420): 'in' (the reference names the pattern variable), 'out' (the
2667
+ * variable is not in scope there) or 'unknown'. A switch-label pattern
2668
+ * reaches its arm; an `instanceof` pattern follows the boolean structure
2669
+ * of its condition (`&&`, `||`, `!`, `?:`, if/while conditions) and, when
2670
+ * introduced while false, the statements after an `if` whose branch
2671
+ * cannot complete normally.
2672
+ */
2673
+ function patternBindingReach(binding, reference) {
2674
+ const within = (outer, inner) => !!outer && outer.startIndex <= inner.startIndex && inner.endIndex <= outer.endIndex;
2675
+ if (reference.startIndex <= binding.startIndex) return 'out';
2676
+ for (let node = binding.parent; node; node = node.parent) {
2677
+ if (node.type === 'switch_label') {
2678
+ const arm = node.parent;
2679
+ return arm && (arm.type === 'switch_rule' || arm.type === 'switch_block_statement_group') &&
2680
+ within(arm, reference) ? 'in' : 'out';
2681
+ }
2682
+ if (node.type === 'instanceof_expression') return instanceofReach(node, reference, within);
2683
+ if (/_statement$|^block$|_declaration$|lambda_expression/.test(node.type)) break;
2684
+ }
2685
+ return 'unknown';
2686
+ }
2687
+
2688
+ function instanceofReach(expression, reference, within) {
2689
+ // A statement that cannot complete normally (null: undecided).
2690
+ const abrupt = statement => {
2691
+ if (!statement) return false;
2692
+ if (['return_statement', 'throw_statement', 'break_statement', 'continue_statement',
2693
+ 'yield_statement'].includes(statement.type)) return true;
2694
+ if (statement.type === 'block') {
2695
+ const last = statement.namedChildren.filter(child => !child.type.includes('comment')).pop();
2696
+ return last ? abrupt(last) : false;
2697
+ }
2698
+ if (['expression_statement', 'local_variable_declaration'].includes(statement.type)) return false;
2699
+ return null;
2700
+ };
2701
+ let polarity = true;
2702
+ let node = expression;
2703
+ for (;;) {
2704
+ const parent = node.parent;
2705
+ if (!parent) return 'unknown';
2706
+ if (parent.type === 'parenthesized_expression') { node = parent; continue; }
2707
+ if (parent.type === 'unary_expression' && parent.childForFieldName('operator')?.type === '!') {
2708
+ polarity = !polarity;
2709
+ node = parent;
2710
+ continue;
2711
+ }
2712
+ if (parent.type === 'binary_expression') {
2713
+ const operator = parent.childForFieldName('operator')?.type;
2714
+ if (operator !== '&&' && operator !== '||') return 'out';
2715
+ if (within(parent.childForFieldName('left'), node) && within(parent.childForFieldName('right'), reference)) {
2716
+ return (operator === '&&') === polarity ? 'in' : 'out';
2717
+ }
2718
+ if ((operator === '&&') !== polarity) return 'out';
2719
+ node = parent;
2720
+ continue;
2721
+ }
2722
+ if (parent.type === 'ternary_expression' && within(parent.childForFieldName('condition'), node)) {
2723
+ if (within(parent.childForFieldName('consequence'), reference)) return polarity ? 'in' : 'out';
2724
+ if (within(parent.childForFieldName('alternative'), reference)) return polarity ? 'out' : 'in';
2725
+ return 'out';
2726
+ }
2727
+ if (['while_statement', 'for_statement', 'do_statement'].includes(parent.type) &&
2728
+ within(parent.childForFieldName('condition'), node)) {
2729
+ if (within(parent.childForFieldName('body'), reference)) {
2730
+ return parent.type !== 'do_statement' && polarity ? 'in' : 'out';
2731
+ }
2732
+ if (within(parent, reference)) return 'out';
2733
+ // After a loop exited through its condition only.
2734
+ return polarity ? 'out' : 'unknown';
2735
+ }
2736
+ if (parent.type === 'if_statement' && within(parent.childForFieldName('condition'), node)) {
2737
+ const consequence = parent.childForFieldName('consequence');
2738
+ const alternative = parent.childForFieldName('alternative');
2739
+ if (within(consequence, reference)) return polarity ? 'in' : 'out';
2740
+ if (alternative && within(alternative, reference)) return polarity ? 'out' : 'in';
2741
+ if (within(parent, reference)) return 'out';
2742
+ // The statements after the `if` in its block (JLS 6.3.2.2).
2743
+ const block = parent.parent;
2744
+ if (!block || !within(block, reference) || !['block', 'switch_block_statement_group',
2745
+ 'constructor_body'].includes(block.type)) return 'out';
2746
+ const taken = polarity ? alternative : consequence;
2747
+ const other = polarity ? consequence : alternative;
2748
+ if (polarity && !alternative) return 'out';
2749
+ const takenAbrupt = abrupt(taken), otherAbrupt = other ? abrupt(other) : false;
2750
+ if (takenAbrupt === null || otherAbrupt === null) return 'unknown';
2751
+ return takenAbrupt && !otherAbrupt ? 'in' : 'out';
2752
+ }
2753
+ return 'out';
2754
+ }
2755
+ }
2756
+
2424
2757
  function findUsagesInCode(code, name, parser, tree) {
2425
2758
  tree = tree || parseTree(parser, code);
2426
2759
  const usages = [];
@@ -2573,7 +2906,18 @@ function isEntryPoint(symbol) {
2573
2906
  return getEntryPointKind(symbol) !== null;
2574
2907
  }
2575
2908
 
2909
+ /** Omitted base initializer calls of one class, read as findCallsInCode reads them (fix #421). */
2910
+ function implicitBaseCallsOf(code, parser, ownerLine, ownerEndLine) {
2911
+ return require('./constructor-bases').implicitBaseCallsOfOwner(parseTree(parser, code).rootNode, 'java', null,
2912
+ ownerLine, ownerEndLine);
2913
+ }
2914
+
2576
2915
  module.exports = {
2916
+ recoverTypeAnnotatedVarargs,
2917
+ implicitOwnerScope: node => ({ enclosingType: enclosingTypeName(node) }),
2918
+ implicitBaseCallsOf,
2919
+ staticValueImportSyntax,
2920
+ patternBindingReach,
2577
2921
  findFunctions,
2578
2922
  findClasses,
2579
2923
  findStateObjects,