ucn 5.5.0 → 5.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/ucn/SKILL.md +35 -9
- package/.claude/skills/ucn/references/commands.md +38 -16
- package/.claude/skills/ucn/references/resolution-rules.md +126 -20
- package/.claude/skills/ucn/references/trust-contract.md +5 -3
- package/README.md +41 -27
- package/cli/index.js +6 -1
- package/core/accessors.js +341 -54
- package/core/account.js +106 -31
- package/core/analysis.js +266 -127
- package/core/ast-analysis.js +12 -3
- package/core/brief.js +8 -4
- package/core/build-worker.js +2 -1
- package/core/cache.js +144 -6
- package/core/call-name-cache.js +134 -0
- package/core/callable-scope.js +21 -0
- package/core/callers.js +5111 -1024
- package/core/check.js +22 -5
- package/core/class-identity.js +399 -12
- package/core/constructor-calls.js +831 -0
- package/core/constructor-hierarchy.js +147 -0
- package/core/constructor-protocol.js +196 -0
- package/core/contract-membership.js +346 -140
- package/core/cpp-conversion-constraints.js +123 -0
- package/core/cpp-conversion-syntax.js +216 -0
- package/core/cpp-conversions.js +414 -0
- package/core/cpp-scope.js +245 -44
- package/core/deadcode.js +35 -15
- package/core/decorator-binding.js +137 -3
- package/core/enum-reference-syntax.js +134 -0
- package/core/execute.js +17 -1
- package/core/external-macros.js +42 -10
- package/core/go-packages.js +171 -0
- package/core/graph-build.js +2 -0
- package/core/graph.js +75 -59
- package/core/ignore-rules.js +2 -1
- package/core/imports.js +145 -19
- package/core/index-ir.js +41 -4
- package/core/ir.js +27 -6
- package/core/macro-expansion.js +38 -2
- package/core/out-of-class-members.js +182 -0
- package/core/output/analysis-ext.js +4 -2
- package/core/output/analysis.js +89 -20
- package/core/output/graph.js +12 -3
- package/core/output/lines.js +4 -3
- package/core/output/public.js +1 -0
- package/core/output/reporting.js +6 -1
- package/core/output/shared.js +2 -0
- package/core/output/tracing.js +28 -22
- package/core/output-budget.js +2 -2
- package/core/parallel-build.js +52 -1
- package/core/project.js +228 -114
- package/core/provenance-constructor-base.js +161 -0
- package/core/provenance-facts.js +257 -27
- package/core/provenance-overload.js +458 -54
- package/core/provenance-type.js +487 -0
- package/core/provenance.js +234 -15
- package/core/public-command.js +17 -2
- package/core/query-bodies.js +159 -0
- package/core/reflection.js +255 -2
- package/core/rename-token-syntax.js +111 -0
- package/core/reporting.js +13 -7
- package/core/rust-macro-expansion.js +1202 -157
- package/core/rust-modules.js +21 -3
- package/core/rust-names.js +607 -0
- package/core/search.js +70 -17
- package/core/shared.js +98 -1
- package/core/source-memo.js +201 -0
- package/core/text-usage-syntax.js +38 -0
- package/core/tracing.js +7 -1
- package/core/type-denotation.js +157 -46
- package/core/type-references.js +615 -246
- package/core/value-references.js +2777 -0
- package/core/verify.js +1097 -281
- package/languages/adapter.js +28 -4
- package/languages/c-family.js +1118 -164
- package/languages/constructor-bases.js +105 -0
- package/languages/cpp-platform.js +26 -0
- package/languages/cpp-site-syntax.js +83 -0
- package/languages/csharp-scope.js +439 -0
- package/languages/csharp.js +428 -68
- package/languages/go.js +385 -207
- package/languages/html.js +4 -2
- package/languages/index.js +397 -2
- package/languages/java-platform.js +226 -0
- package/languages/java-unicode.js +269 -0
- package/languages/java.js +461 -117
- package/languages/javascript.js +418 -66
- package/languages/lexical-scope.js +190 -23
- package/languages/python.js +404 -54
- package/languages/rust.js +548 -89
- package/languages/utils.js +67 -8
- package/mcp/server.js +8 -2
- package/package.json +2 -2
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
|
-
|
|
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
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
? {
|
|
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),
|
|
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 ?
|
|
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
|
-
(
|
|
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
|
|
1674
|
-
//
|
|
1675
|
-
//
|
|
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
|
-
|
|
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
|
-
(
|
|
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
|
-
?
|
|
1953
|
-
|
|
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, ...(
|
|
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
|
|
2006
|
-
//
|
|
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 &&
|
|
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
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
: null;
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
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,
|
|
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 =
|
|
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,
|