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