gitnexus 1.6.10-rc.98 → 1.6.10-rc.99
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/dist/core/ingestion/class-extractors/configs/jvm.js +6 -6
- package/dist/core/ingestion/languages/java/captures.js +52 -12
- package/dist/core/ingestion/languages/java/query.js +1 -0
- package/dist/core/ingestion/scope-extractor.js +2 -1
- package/dist/core/ingestion/type-extractors/jvm.js +2 -2
- package/dist/core/ingestion/utils/ast-helpers.d.ts +25 -1
- package/dist/core/ingestion/utils/ast-helpers.js +149 -104
- package/dist/storage/parse-cache.js +3 -0
- package/dist/storage/repo-manager.d.ts +8 -2
- package/dist/storage/repo-manager.js +7 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// gitnexus/src/core/ingestion/class-extractors/configs/jvm.ts
|
|
2
2
|
import { SupportedLanguages } from '../../../../_shared/index.js';
|
|
3
|
-
import {
|
|
3
|
+
import { synthesizeJavaTypeIdentity } from '../../utils/ast-helpers.js';
|
|
4
4
|
// ---------------------------------------------------------------------------
|
|
5
5
|
// Java
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
@@ -29,10 +29,10 @@ export const javaClassConfig = {
|
|
|
29
29
|
'record_declaration',
|
|
30
30
|
],
|
|
31
31
|
extractName(node) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return
|
|
32
|
+
return synthesizeJavaTypeIdentity(node)?.name;
|
|
33
|
+
},
|
|
34
|
+
extractType(node) {
|
|
35
|
+
return synthesizeJavaTypeIdentity(node)?.label;
|
|
36
36
|
},
|
|
37
37
|
// An anonymous body whose name CANNOT be synthesized must not become a
|
|
38
38
|
// Class node at all. Without this skip, `extract()`'s
|
|
@@ -45,7 +45,7 @@ export const javaClassConfig = {
|
|
|
45
45
|
definitionNode !== undefined &&
|
|
46
46
|
(definitionNode.type === 'object_creation_expression' ||
|
|
47
47
|
definitionNode.type === 'enum_constant') &&
|
|
48
|
-
|
|
48
|
+
synthesizeJavaTypeIdentity(definitionNode) === undefined);
|
|
49
49
|
},
|
|
50
50
|
};
|
|
51
51
|
// ---------------------------------------------------------------------------
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { materializeClassAnnotationFacts, recordClassAnnotationCapture, } from '../../frameworks/spring/bean-candidates.js';
|
|
2
|
-
import { nodeIfType, nodeToCapture,
|
|
2
|
+
import { javaLocalTypeDeclarationContainer, nodeIfType, nodeToCapture, synthesizeJavaTypeIdentity, syntheticCapture, } from '../../utils/ast-helpers.js';
|
|
3
3
|
import { splitImportDeclaration } from './import-decomposer.js';
|
|
4
4
|
import { computeJavaArityMetadata } from './arity-metadata.js';
|
|
5
5
|
import { synthesizeJavaReceiverBinding } from './receiver-binding.js';
|
|
@@ -15,7 +15,11 @@ import { captureJavaSpringDiClassFact } from './spring-di.js';
|
|
|
15
15
|
/** Declaration anchors that carry function-like arity metadata. */
|
|
16
16
|
const FUNCTION_DECL_TAGS = ['@declaration.method', '@declaration.constructor'];
|
|
17
17
|
/** tree-sitter-java node types that the method extractor accepts. */
|
|
18
|
-
const FUNCTION_NODE_TYPES = [
|
|
18
|
+
const FUNCTION_NODE_TYPES = [
|
|
19
|
+
'method_declaration',
|
|
20
|
+
'constructor_declaration',
|
|
21
|
+
'compact_constructor_declaration',
|
|
22
|
+
];
|
|
19
23
|
const JAVA_CALLABLE_CAPTURE_OPTIONS = {
|
|
20
24
|
functionNodeTypes: new Set([...FUNCTION_NODE_TYPES, 'lambda_expression']),
|
|
21
25
|
callNodeTypes: new Set(['method_invocation']),
|
|
@@ -34,6 +38,26 @@ const JAVA_CALLABLE_CAPTURE_OPTIONS = {
|
|
|
34
38
|
callableProtocolMethods: new Set(['run', 'apply', 'accept', 'call']),
|
|
35
39
|
normalizeQualifiedName: (raw) => raw.replaceAll('::', '.'),
|
|
36
40
|
};
|
|
41
|
+
/** Visibility of a local type begins at its declaration and ends with its
|
|
42
|
+
* immediately enclosing block (JLS 6.3). A Java-only synthetic Block scope
|
|
43
|
+
* models that range without changing shared resolver selection semantics. */
|
|
44
|
+
function javaLocalTypeVisibilityScope(node) {
|
|
45
|
+
const container = javaLocalTypeDeclarationContainer(node);
|
|
46
|
+
if (container === null)
|
|
47
|
+
return undefined;
|
|
48
|
+
return {
|
|
49
|
+
'@scope.block': {
|
|
50
|
+
name: '@scope.block',
|
|
51
|
+
range: {
|
|
52
|
+
startLine: node.startPosition.row + 1,
|
|
53
|
+
startCol: node.startPosition.column,
|
|
54
|
+
endLine: container.endPosition.row + 1,
|
|
55
|
+
endCol: container.endPosition.column,
|
|
56
|
+
},
|
|
57
|
+
text: node.text,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
37
61
|
/** Suppress read.member emissions when the field_access is already
|
|
38
62
|
* covered by a method_invocation (object of a call) or an
|
|
39
63
|
* assignment_expression (write target). */
|
|
@@ -96,6 +120,22 @@ export function emitJavaScopeCaptures(sourceText, filePath, cachedTree) {
|
|
|
96
120
|
recordClassAnnotationCapture(classAnnotations, filePath, annotatedClass, annotationName.text);
|
|
97
121
|
continue;
|
|
98
122
|
}
|
|
123
|
+
const typeDeclaration = [
|
|
124
|
+
nodeMap['@declaration.class'],
|
|
125
|
+
nodeMap['@declaration.enum'],
|
|
126
|
+
nodeMap['@declaration.record'],
|
|
127
|
+
nodeMap['@declaration.interface'],
|
|
128
|
+
].find((node) => node !== undefined);
|
|
129
|
+
const localTypeIdentity = typeDeclaration === undefined ? undefined : synthesizeJavaTypeIdentity(typeDeclaration);
|
|
130
|
+
if (localTypeIdentity?.bindingName !== undefined &&
|
|
131
|
+
grouped['@declaration.name'] !== undefined &&
|
|
132
|
+
typeDeclaration !== undefined) {
|
|
133
|
+
grouped['@declaration.binding-name'] = grouped['@declaration.name'];
|
|
134
|
+
grouped['@declaration.name'] = syntheticCapture('@declaration.name', typeDeclaration, localTypeIdentity.name);
|
|
135
|
+
const visibilityScope = javaLocalTypeVisibilityScope(typeDeclaration);
|
|
136
|
+
if (visibilityScope !== undefined)
|
|
137
|
+
out.push(visibilityScope);
|
|
138
|
+
}
|
|
99
139
|
// Decompose each `import_declaration`. `@import.statement` is captured
|
|
100
140
|
// directly on the `import_declaration` node.
|
|
101
141
|
if (grouped['@import.statement'] !== undefined) {
|
|
@@ -218,8 +258,8 @@ export function emitJavaScopeCaptures(sourceText, filePath, cachedTree) {
|
|
|
218
258
|
}
|
|
219
259
|
/**
|
|
220
260
|
* Synthesize `@declaration.class` matches for anonymous class bodies
|
|
221
|
-
* (`new Runnable() { ... }`), named by the same javac-
|
|
222
|
-
* (`
|
|
261
|
+
* (`new Runnable() { ... }`), named by the same javac-compatible authority
|
|
262
|
+
* (`synthesizeJavaTypeIdentity` → `Worker$N`) the structure phase
|
|
223
263
|
* uses — the two layers agree by construction (#2550).
|
|
224
264
|
*
|
|
225
265
|
* The anchor is the `class_body` node: it shares its range with the
|
|
@@ -232,15 +272,15 @@ export function emitJavaScopeCaptures(sourceText, filePath, cachedTree) {
|
|
|
232
272
|
function synthesizeJavaAnonymousClassDeclarations(rootNode) {
|
|
233
273
|
const out = [];
|
|
234
274
|
for (const oce of rootNode.descendantsOfType('object_creation_expression')) {
|
|
235
|
-
const
|
|
236
|
-
if (
|
|
275
|
+
const identity = synthesizeJavaTypeIdentity(oce);
|
|
276
|
+
if (identity === undefined)
|
|
237
277
|
continue;
|
|
238
278
|
const body = oce.namedChildren.find((c) => c.type === 'class_body');
|
|
239
279
|
if (body === undefined)
|
|
240
280
|
continue;
|
|
241
281
|
out.push({
|
|
242
282
|
'@declaration.class': nodeToCapture('@declaration.class', body),
|
|
243
|
-
'@declaration.name': syntheticCapture('@declaration.name', body, name),
|
|
283
|
+
'@declaration.name': syntheticCapture('@declaration.name', body, identity.name),
|
|
244
284
|
});
|
|
245
285
|
// Inheritance: the anonymous class extends/implements its constructed
|
|
246
286
|
// type. Anchor the `@reference.inherits` on the `class_body` — its
|
|
@@ -278,7 +318,7 @@ function synthesizeJavaAnonymousClassDeclarations(rootNode) {
|
|
|
278
318
|
out.push({
|
|
279
319
|
'@type-binding.annotation': nodeToCapture('@type-binding.annotation', declNode),
|
|
280
320
|
'@type-binding.name': nodeToCapture('@type-binding.name', varName),
|
|
281
|
-
'@type-binding.type': syntheticCapture('@type-binding.type', oce, name),
|
|
321
|
+
'@type-binding.type': syntheticCapture('@type-binding.type', oce, identity.name),
|
|
282
322
|
});
|
|
283
323
|
}
|
|
284
324
|
}
|
|
@@ -293,11 +333,11 @@ function synthesizeJavaAnonymousClassDeclarations(rootNode) {
|
|
|
293
333
|
const hostEnum = javaEnclosingEnumNameOf(constant);
|
|
294
334
|
const bodyNode = constant.childForFieldName?.('body');
|
|
295
335
|
const isBodied = bodyNode !== null && bodyNode !== undefined && bodyNode.type === 'class_body';
|
|
296
|
-
const
|
|
297
|
-
if (
|
|
336
|
+
const bodiedIdentity = synthesizeJavaTypeIdentity(constant);
|
|
337
|
+
if (bodiedIdentity !== undefined && isBodied) {
|
|
298
338
|
out.push({
|
|
299
339
|
'@declaration.class': nodeToCapture('@declaration.class', bodyNode),
|
|
300
|
-
'@declaration.name': syntheticCapture('@declaration.name', bodyNode,
|
|
340
|
+
'@declaration.name': syntheticCapture('@declaration.name', bodyNode, bodiedIdentity.name),
|
|
301
341
|
});
|
|
302
342
|
if (hostEnum !== undefined) {
|
|
303
343
|
out.push({
|
|
@@ -324,7 +364,7 @@ function synthesizeJavaAnonymousClassDeclarations(rootNode) {
|
|
|
324
364
|
// the `object_creation_expression` branch, which skips on synthesis
|
|
325
365
|
// failure. `hostEnum` is used only for genuinely body-less constants.
|
|
326
366
|
const constantNameNode = constant.childForFieldName?.('name');
|
|
327
|
-
const constantType = isBodied ?
|
|
367
|
+
const constantType = isBodied ? bodiedIdentity?.name : hostEnum;
|
|
328
368
|
if (constantNameNode !== null && constantNameNode !== undefined && constantType !== undefined) {
|
|
329
369
|
out.push({
|
|
330
370
|
'@type-binding.annotation': nodeToCapture('@type-binding.annotation', constant),
|
|
@@ -522,7 +522,8 @@ function parseJsonStringArrayCapture(cap) {
|
|
|
522
522
|
}
|
|
523
523
|
}
|
|
524
524
|
function deriveDeclarationName(match, def) {
|
|
525
|
-
const nameCap = match['@declaration.name'] ??
|
|
525
|
+
const nameCap = match['@declaration.binding-name'] ??
|
|
526
|
+
match['@declaration.name'] ??
|
|
526
527
|
match[Object.keys(match).find((k) => k.startsWith('@declaration.') && k.endsWith('.name')) ?? ''];
|
|
527
528
|
if (nameCap !== undefined)
|
|
528
529
|
return nameCap.text;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { findChild,
|
|
1
|
+
import { findChild, synthesizeJavaTypeIdentity } from '../utils/ast-helpers.js';
|
|
2
2
|
import { extractSimpleTypeName, extractVarName, extractGenericTypeArgs, resolveIterableElementType, methodToTypeArgPosition, extractElementTypeFromString, } from './shared.js';
|
|
3
3
|
// ── Java ──────────────────────────────────────────────────────────────────
|
|
4
4
|
const JAVA_DECLARATION_NODE_TYPES = new Set([
|
|
@@ -13,7 +13,7 @@ const anonymousInitializerTypeName = (declarator) => {
|
|
|
13
13
|
const valueNode = declarator.childForFieldName('value');
|
|
14
14
|
if (!valueNode || valueNode.type !== 'object_creation_expression')
|
|
15
15
|
return undefined;
|
|
16
|
-
return
|
|
16
|
+
return synthesizeJavaTypeIdentity(valueNode)?.name;
|
|
17
17
|
};
|
|
18
18
|
/** Java: Type x = ...; Type x; */
|
|
19
19
|
const extractJavaDeclaration = (node, env) => {
|
|
@@ -102,7 +102,31 @@ export interface EnclosingClassInfo {
|
|
|
102
102
|
*/
|
|
103
103
|
qualifiedClassId?: string;
|
|
104
104
|
}
|
|
105
|
-
|
|
105
|
+
/**
|
|
106
|
+
* GitNexus's source-type-relative Java identity for local and anonymous
|
|
107
|
+
* types. It follows javac's `$N` allocation but intentionally omits the
|
|
108
|
+
* package prefix because graph ids already include the source file path.
|
|
109
|
+
*/
|
|
110
|
+
export interface JavaSynthesizedTypeIdentity {
|
|
111
|
+
readonly name: string;
|
|
112
|
+
readonly label: 'Class' | 'Enum' | 'Record' | 'Interface';
|
|
113
|
+
readonly bindingName?: string;
|
|
114
|
+
}
|
|
115
|
+
/** A legal local type declaration is a class, enum, record, or interface
|
|
116
|
+
* directly occupying a block-statement position. Annotation interfaces are
|
|
117
|
+
* deliberately excluded: javac rejects local annotation declarations. */
|
|
118
|
+
export declare const javaLocalTypeDeclarationContainer: (node: SyntaxNode) => SyntaxNode | null;
|
|
119
|
+
/**
|
|
120
|
+
* Authoritative Java local/anonymous type identity.
|
|
121
|
+
*
|
|
122
|
+
* JLS 13.1 defines the shape and immediate-host prefix. OpenJDK javac's
|
|
123
|
+
* Check.localClassName allocates N independently for each
|
|
124
|
+
* (enclosing binary name, local simple name) pair; anonymous types use the
|
|
125
|
+
* empty simple name and therefore have their own sequence. Package names are
|
|
126
|
+
* omitted from this project identity because graph ids already include the
|
|
127
|
+
* file path.
|
|
128
|
+
*/
|
|
129
|
+
export declare const synthesizeJavaTypeIdentity: (node: SyntaxNode) => JavaSynthesizedTypeIdentity | undefined;
|
|
106
130
|
export declare const findEnclosingClassInfo: (node: SyntaxNode, filePath: string, resolveEnclosingOwner?: (node: SyntaxNode) => SyntaxNode | null,
|
|
107
131
|
/**
|
|
108
132
|
* Optional (#1978): returns the enclosing type's fully-qualified name
|
|
@@ -360,31 +360,29 @@ export function getLabelFromCaptures(captureMap, provider) {
|
|
|
360
360
|
* `MAX_ENCLOSING_WALK_ITERATIONS` defense-in-depth guard below prevents
|
|
361
361
|
* pathological hooks from creating an infinite loop. */
|
|
362
362
|
const MAX_ENCLOSING_WALK_ITERATIONS = 4096;
|
|
363
|
-
/**
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
* this one helper.
|
|
376
|
-
*/
|
|
377
|
-
/** Type-declaration node types that can host (and name) a Java anonymous
|
|
378
|
-
* class body. Naming follows JLS 13.1: the binary name is the
|
|
379
|
-
* IMMEDIATELY enclosing type's binary name + `$N`, so the synthesized
|
|
380
|
-
* name is the `$`-joined chain of enclosing host names
|
|
381
|
-
* (`EnumWrap$Mode$1`), numbered per immediate host in source order. */
|
|
382
|
-
const JAVA_ANON_HOST_TYPES = new Set([
|
|
383
|
-
'class_declaration',
|
|
384
|
-
'enum_declaration',
|
|
385
|
-
'interface_declaration',
|
|
386
|
-
'record_declaration',
|
|
363
|
+
/** Named Java declarations that can host, or themselves be, local types. */
|
|
364
|
+
const JAVA_NAMED_TYPE_NODE_LABELS = new Map([
|
|
365
|
+
['class_declaration', 'Class'],
|
|
366
|
+
['enum_declaration', 'Enum'],
|
|
367
|
+
['interface_declaration', 'Interface'],
|
|
368
|
+
['record_declaration', 'Record'],
|
|
369
|
+
]);
|
|
370
|
+
const JAVA_ANON_HOST_TYPES = new Set(JAVA_NAMED_TYPE_NODE_LABELS.keys());
|
|
371
|
+
const JAVA_LOCAL_TYPE_CONTAINERS = new Set([
|
|
372
|
+
'block',
|
|
373
|
+
'constructor_body',
|
|
374
|
+
'switch_block_statement_group',
|
|
387
375
|
]);
|
|
376
|
+
/** A legal local type declaration is a class, enum, record, or interface
|
|
377
|
+
* directly occupying a block-statement position. Annotation interfaces are
|
|
378
|
+
* deliberately excluded: javac rejects local annotation declarations. */
|
|
379
|
+
export const javaLocalTypeDeclarationContainer = (node) => {
|
|
380
|
+
if (!JAVA_NAMED_TYPE_NODE_LABELS.has(node.type))
|
|
381
|
+
return null;
|
|
382
|
+
const parent = node.parent;
|
|
383
|
+
return parent !== null && JAVA_LOCAL_TYPE_CONTAINERS.has(parent.type) ? parent : null;
|
|
384
|
+
};
|
|
385
|
+
const isJavaLocalTypeNode = (node) => javaLocalTypeDeclarationContainer(node) !== null;
|
|
388
386
|
/** The two Java anonymous-class-body shapes (#2550/#2555): an
|
|
389
387
|
* `object_creation_expression` with a `class_body` child
|
|
390
388
|
* (`new Runnable() { ... }`), and an `enum_constant` with a `body:`
|
|
@@ -392,10 +390,7 @@ const JAVA_ANON_HOST_TYPES = new Set([
|
|
|
392
390
|
const isJavaAnonymousBodyNode = (node) => (node.type === 'object_creation_expression' &&
|
|
393
391
|
node.namedChildren?.some((c) => c.type === 'class_body') === true) ||
|
|
394
392
|
(node.type === 'enum_constant' && node.childForFieldName?.('body')?.type === 'class_body');
|
|
395
|
-
/** Nearest ancestor of `node` that is an enclosing
|
|
396
|
-
* a named host declaration OR another anonymous body (both shapes).
|
|
397
|
-
* Anonymous ancestors chain through: an anon inside an anon is
|
|
398
|
-
* `Host$1$1`, and an anon inside an enum constant body is `E$1$1`. */
|
|
393
|
+
/** Nearest ancestor of `node` that is an enclosing type per JLS 13.1. */
|
|
399
394
|
const nearestJavaEnclosingType = (node) => {
|
|
400
395
|
let cursor = node.parent;
|
|
401
396
|
let iterations = 0;
|
|
@@ -408,84 +403,134 @@ const nearestJavaEnclosingType = (node) => {
|
|
|
408
403
|
}
|
|
409
404
|
return null;
|
|
410
405
|
};
|
|
411
|
-
/**
|
|
412
|
-
*
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
406
|
+
/** Parse-tree-bounded memo. Sequence ordinals are built once per tree, avoiding
|
|
407
|
+
* a host-candidate scan for every extraction/ownership consumer. */
|
|
408
|
+
const javaTypeIdentityMemo = new WeakMap();
|
|
409
|
+
const javaHostKey = (node) => `${node.type}:${node.startIndex}`;
|
|
410
|
+
const javaIdentityCandidatesBelow = (root) => {
|
|
411
|
+
const seen = new Set();
|
|
412
|
+
const candidates = [];
|
|
413
|
+
for (const type of [
|
|
414
|
+
'object_creation_expression',
|
|
415
|
+
'enum_constant',
|
|
416
|
+
...JAVA_NAMED_TYPE_NODE_LABELS.keys(),
|
|
417
|
+
]) {
|
|
418
|
+
for (const candidate of root.descendantsOfType?.(type) ?? []) {
|
|
419
|
+
if (!isJavaAnonymousBodyNode(candidate) && !isJavaLocalTypeNode(candidate))
|
|
420
|
+
continue;
|
|
421
|
+
const key = javaHostKey(candidate);
|
|
422
|
+
if (seen.has(key))
|
|
423
|
+
continue;
|
|
424
|
+
seen.add(key);
|
|
425
|
+
candidates.push(candidate);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return candidates.sort((left, right) => left.startIndex - right.startIndex);
|
|
429
|
+
};
|
|
430
|
+
const buildJavaTypeIdentityState = (root) => {
|
|
431
|
+
const ordinalByStart = new Map();
|
|
432
|
+
const sequenceCounts = new Map();
|
|
433
|
+
for (const candidate of javaIdentityCandidatesBelow(root)) {
|
|
434
|
+
const host = nearestJavaEnclosingType(candidate);
|
|
435
|
+
if (host === null)
|
|
436
|
+
continue;
|
|
437
|
+
const isAnonymous = isJavaAnonymousBodyNode(candidate);
|
|
438
|
+
const bindingName = isAnonymous ? '' : candidate.childForFieldName?.('name')?.text;
|
|
439
|
+
// Anonymous types deliberately use the empty sequence key; malformed named
|
|
440
|
+
// declarations must not enter that sequence.
|
|
441
|
+
if (!isAnonymous && !bindingName)
|
|
442
|
+
continue;
|
|
443
|
+
const sequenceKey = `${javaHostKey(host)}:${bindingName}`;
|
|
444
|
+
const ordinal = (sequenceCounts.get(sequenceKey) ?? 0) + 1;
|
|
445
|
+
sequenceCounts.set(sequenceKey, ordinal);
|
|
446
|
+
ordinalByStart.set(candidate.startIndex, ordinal);
|
|
447
|
+
}
|
|
448
|
+
return { byStart: new Map(), ordinalByStart };
|
|
449
|
+
};
|
|
450
|
+
const javaTypeIdentityStateFor = (node) => {
|
|
421
451
|
const tree = node.tree;
|
|
422
|
-
if (tree
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
452
|
+
if (tree === undefined) {
|
|
453
|
+
const host = nearestJavaEnclosingType(node);
|
|
454
|
+
return buildJavaTypeIdentityState(host ?? node);
|
|
455
|
+
}
|
|
456
|
+
let state = javaTypeIdentityMemo.get(tree);
|
|
457
|
+
if (state === undefined) {
|
|
458
|
+
state = buildJavaTypeIdentityState(tree.rootNode ?? node);
|
|
459
|
+
javaTypeIdentityMemo.set(tree, state);
|
|
460
|
+
}
|
|
461
|
+
return state;
|
|
462
|
+
};
|
|
463
|
+
/** Source-type-relative binary name of a Java enclosing type, including
|
|
464
|
+
* synthesized local/anonymous hosts and named member-type chains. */
|
|
465
|
+
const javaBinaryNameOfType = (node) => {
|
|
466
|
+
if (isJavaAnonymousBodyNode(node) || isJavaLocalTypeNode(node)) {
|
|
467
|
+
return synthesizeJavaTypeIdentity(node)?.name;
|
|
426
468
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
// is the `$`-joined chain of named hosts (`EnumWrap$Mode`).
|
|
469
|
+
if (!JAVA_ANON_HOST_TYPES.has(node.type))
|
|
470
|
+
return undefined;
|
|
471
|
+
const simpleName = node.childForFieldName?.('name')?.text;
|
|
472
|
+
if (simpleName === undefined || simpleName.length === 0)
|
|
473
|
+
return undefined;
|
|
433
474
|
const enclosing = nearestJavaEnclosingType(node);
|
|
434
475
|
if (enclosing === null)
|
|
476
|
+
return simpleName;
|
|
477
|
+
const enclosingName = javaBinaryNameOfType(enclosing);
|
|
478
|
+
return enclosingName === undefined ? undefined : `${enclosingName}$${simpleName}`;
|
|
479
|
+
};
|
|
480
|
+
/**
|
|
481
|
+
* Authoritative Java local/anonymous type identity.
|
|
482
|
+
*
|
|
483
|
+
* JLS 13.1 defines the shape and immediate-host prefix. OpenJDK javac's
|
|
484
|
+
* Check.localClassName allocates N independently for each
|
|
485
|
+
* (enclosing binary name, local simple name) pair; anonymous types use the
|
|
486
|
+
* empty simple name and therefore have their own sequence. Package names are
|
|
487
|
+
* omitted from this project identity because graph ids already include the
|
|
488
|
+
* file path.
|
|
489
|
+
*/
|
|
490
|
+
export const synthesizeJavaTypeIdentity = (node) => {
|
|
491
|
+
const localLabel = JAVA_NAMED_TYPE_NODE_LABELS.get(node.type);
|
|
492
|
+
const isLocal = localLabel !== undefined && isJavaLocalTypeNode(node);
|
|
493
|
+
const isAnonymous = isJavaAnonymousBodyNode(node);
|
|
494
|
+
const enclosing = nearestJavaEnclosingType(node);
|
|
495
|
+
const memberSimpleName = !isLocal && !isAnonymous && localLabel !== undefined
|
|
496
|
+
? node.childForFieldName?.('name')?.text
|
|
497
|
+
: undefined;
|
|
498
|
+
const synthesizedHostIdentity = memberSimpleName !== undefined && enclosing !== null
|
|
499
|
+
? synthesizeJavaTypeIdentity(enclosing)
|
|
500
|
+
: undefined;
|
|
501
|
+
if (!isLocal && !isAnonymous && synthesizedHostIdentity === undefined)
|
|
435
502
|
return undefined;
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
return undefined;
|
|
454
|
-
hostNames.unshift(hostName);
|
|
455
|
-
}
|
|
456
|
-
cursor = cursor.parent;
|
|
457
|
-
}
|
|
458
|
-
prefix = hostNames.join('$');
|
|
459
|
-
}
|
|
460
|
-
// All anonymous bodies (both shapes) whose immediately enclosing TYPE
|
|
461
|
-
// is THIS one, in source order. `descendantsOfType` over the subtree
|
|
462
|
-
// also finds bodies belonging to nested enclosing types — filter them
|
|
463
|
-
// out by re-deriving each candidate's own enclosing type.
|
|
464
|
-
const candidates = [
|
|
465
|
-
...(enclosing.descendantsOfType?.('object_creation_expression') ?? []),
|
|
466
|
-
...(enclosing.descendantsOfType?.('enum_constant') ?? []),
|
|
467
|
-
]
|
|
468
|
-
.filter(isJavaAnonymousBodyNode)
|
|
469
|
-
.filter((c) => {
|
|
470
|
-
const host = nearestJavaEnclosingType(c);
|
|
471
|
-
return (host !== null && host.startIndex === enclosing.startIndex && host.type === enclosing.type);
|
|
472
|
-
})
|
|
473
|
-
.sort((a, b) => a.startIndex - b.startIndex);
|
|
474
|
-
if (tree !== undefined) {
|
|
475
|
-
let byStart = javaAnonNameMemo.get(tree);
|
|
476
|
-
if (byStart === undefined) {
|
|
477
|
-
byStart = new Map();
|
|
478
|
-
javaAnonNameMemo.set(tree, byStart);
|
|
479
|
-
}
|
|
480
|
-
for (let i = 0; i < candidates.length; i++) {
|
|
481
|
-
byStart.set(candidates[i].startIndex, `${prefix}$${i + 1}`);
|
|
482
|
-
}
|
|
483
|
-
return byStart.get(node.startIndex);
|
|
503
|
+
if (enclosing === null)
|
|
504
|
+
return undefined;
|
|
505
|
+
const state = javaTypeIdentityStateFor(node);
|
|
506
|
+
const cached = state.byStart.get(node.startIndex);
|
|
507
|
+
if (cached !== undefined)
|
|
508
|
+
return cached;
|
|
509
|
+
const prefix = javaBinaryNameOfType(enclosing);
|
|
510
|
+
if (prefix === undefined)
|
|
511
|
+
return undefined;
|
|
512
|
+
if (memberSimpleName !== undefined) {
|
|
513
|
+
const identity = {
|
|
514
|
+
name: `${prefix}$${memberSimpleName}`,
|
|
515
|
+
label: localLabel,
|
|
516
|
+
bindingName: memberSimpleName,
|
|
517
|
+
};
|
|
518
|
+
state.byStart.set(node.startIndex, identity);
|
|
519
|
+
return identity;
|
|
484
520
|
}
|
|
485
|
-
const
|
|
486
|
-
if (
|
|
521
|
+
const bindingName = isLocal ? node.childForFieldName?.('name')?.text : undefined;
|
|
522
|
+
if (isLocal && !bindingName)
|
|
487
523
|
return undefined;
|
|
488
|
-
|
|
524
|
+
const ordinal = state.ordinalByStart.get(node.startIndex);
|
|
525
|
+
if (ordinal === undefined)
|
|
526
|
+
return undefined;
|
|
527
|
+
const identity = {
|
|
528
|
+
name: `${prefix}$${ordinal}${bindingName ?? ''}`,
|
|
529
|
+
label: isAnonymous ? 'Class' : localLabel,
|
|
530
|
+
...(bindingName === undefined ? {} : { bindingName }),
|
|
531
|
+
};
|
|
532
|
+
state.byStart.set(node.startIndex, identity);
|
|
533
|
+
return identity;
|
|
489
534
|
};
|
|
490
535
|
export const findEnclosingClassInfo = (node, filePath, resolveEnclosingOwner,
|
|
491
536
|
/**
|
|
@@ -553,12 +598,12 @@ getQualifiedOwnerName) => {
|
|
|
553
598
|
// enum constant, and every C# `object_creation_expression`), so the
|
|
554
599
|
// walk continues unchanged for those — including on to
|
|
555
600
|
// `enum_declaration`, which sits in CLASS_CONTAINER_TYPES below.
|
|
556
|
-
if (current
|
|
557
|
-
const
|
|
558
|
-
if (
|
|
601
|
+
if (isJavaAnonymousBodyNode(current) || JAVA_ANON_HOST_TYPES.has(current.type)) {
|
|
602
|
+
const identity = synthesizeJavaTypeIdentity(current);
|
|
603
|
+
if (identity !== undefined) {
|
|
559
604
|
return {
|
|
560
|
-
classId: generateId(
|
|
561
|
-
className:
|
|
605
|
+
classId: generateId(identity.label, `${filePath}:${identity.name}`),
|
|
606
|
+
className: identity.name,
|
|
562
607
|
};
|
|
563
608
|
}
|
|
564
609
|
}
|
|
@@ -56,6 +56,9 @@ import { fileURLToPath } from 'url';
|
|
|
56
56
|
// method injection sites plus bean-name and @Primary provider metadata.
|
|
57
57
|
// v20: Java/Kotlin capture side-channels persist package and class-annotation
|
|
58
58
|
// facts for shared Spring Bean resolution.
|
|
59
|
+
// v21: Java local class/enum/record/interface captures use javac-compatible,
|
|
60
|
+
// source-type-relative JLS 13.1 identities and declaration-to-block scopes
|
|
61
|
+
// (#2562).
|
|
59
62
|
// v19: Java enum constant bodies emit E$N Class nodes; anonymous naming uses
|
|
60
63
|
// JLS 13.1 immediate-host chains (#2555).
|
|
61
64
|
// v18: Worker$N anonymous bodies. v17: callable-value-flow operand identity.
|
|
@@ -431,8 +431,14 @@ export interface RepoMeta {
|
|
|
431
431
|
* spurious edges and these new resolved edges are cross-file, so a pre-v12
|
|
432
432
|
* top-up would leave unchanged Rust files stale either way; force a full
|
|
433
433
|
* re-analyze instead.
|
|
434
|
-
|
|
435
|
-
|
|
434
|
+
* v13: Java local classes, enums, records, and interfaces use
|
|
435
|
+
* source-type-relative JLS 13.1 identities (`Outer$1Local`). Number allocation
|
|
436
|
+
* matches javac: one sequence per (enclosing type, local simple name), with a
|
|
437
|
+
* separate sequence for anonymous types. Existing type/member ids, lexical
|
|
438
|
+
* bindings, and ownership edges must not be mixed with newly named unchanged
|
|
439
|
+
* Java files; force a full re-analyze.
|
|
440
|
+
*/
|
|
441
|
+
export declare const INCREMENTAL_SCHEMA_VERSION = 13;
|
|
436
442
|
export interface IndexedRepo {
|
|
437
443
|
repoPath: string;
|
|
438
444
|
storagePath: string;
|
|
@@ -152,8 +152,14 @@ export const cloneDirBelongsToEntry = (cloneDir, entryPath) => registryPathEqual
|
|
|
152
152
|
* spurious edges and these new resolved edges are cross-file, so a pre-v12
|
|
153
153
|
* top-up would leave unchanged Rust files stale either way; force a full
|
|
154
154
|
* re-analyze instead.
|
|
155
|
+
* v13: Java local classes, enums, records, and interfaces use
|
|
156
|
+
* source-type-relative JLS 13.1 identities (`Outer$1Local`). Number allocation
|
|
157
|
+
* matches javac: one sequence per (enclosing type, local simple name), with a
|
|
158
|
+
* separate sequence for anonymous types. Existing type/member ids, lexical
|
|
159
|
+
* bindings, and ownership edges must not be mixed with newly named unchanged
|
|
160
|
+
* Java files; force a full re-analyze.
|
|
155
161
|
*/
|
|
156
|
-
export const INCREMENTAL_SCHEMA_VERSION =
|
|
162
|
+
export const INCREMENTAL_SCHEMA_VERSION = 13;
|
|
157
163
|
const GITNEXUS_DIR = '.gitnexus';
|
|
158
164
|
const GITNEXUS_EXCLUDE_ENTRY = `${GITNEXUS_DIR}/`;
|
|
159
165
|
export const INDEX_METADATA_FILE = 'gitnexus.json';
|
package/package.json
CHANGED