mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-11-11 08:37:35 +00:00
fix(java): emit Record interface heritage
Synthesize inheritance references for Java record implements clauses so scope resolution emits canonical heritage and interface-dispatch edges.
This commit is contained in:
parent
49c5b7d81f
commit
9615f098c8
3 changed files with 63 additions and 4 deletions
|
|
@ -638,13 +638,17 @@ function findEnclosingTypeDeclaration(node: SyntaxNode): SyntaxNode | null {
|
|||
* is dropped for registry-primary languages in the worker pipeline (issue #1951).
|
||||
*
|
||||
* Scope covers `class_declaration` (`superclass` extends + `interfaces`
|
||||
* implements clauses) AND `interface_declaration` (`extends_interfaces` →
|
||||
* interface-to-interface EXTENDS), matching the legacy Java heritage query
|
||||
* implements clauses), `record_declaration` (`interfaces` implements clauses),
|
||||
* AND `interface_declaration` (`extends_interfaces` → interface-to-interface
|
||||
* EXTENDS), matching the legacy Java heritage query
|
||||
* (tree-sitter-queries.ts), which has a dedicated `interface_declaration
|
||||
* (extends_interfaces (type_list …))` arm. Without the interface arm the
|
||||
* registry-primary synth silently dropped every `interface IA extends IB`
|
||||
* edge while the legacy leg emitted it — the exact =0/=N parity break #1951
|
||||
* targets. Enum/record heritage stays unemitted (no legacy arm). Generic
|
||||
* targets. Record heritage is emitted even though the legacy query had no
|
||||
* matching arm: Record graph nodes are canonical link targets since #2801, so
|
||||
* dropping their `implements` clause prevents interface dispatch (#2900). Enum
|
||||
* heritage remains unemitted. Generic
|
||||
* bases (`extends Box<T>`, `implements IFoo<T>`) ARE emitted here: the legacy
|
||||
* heritage query was widened to capture the inner `type_identifier` of a
|
||||
* `generic_type` (tree-sitter-queries.ts), so both paths now agree on SIMPLE
|
||||
|
|
@ -671,7 +675,7 @@ function synthesizeJavaInheritanceReferences(root: SyntaxNode): CaptureMatch[] {
|
|||
const stack: SyntaxNode[] = [root];
|
||||
while (stack.length > 0) {
|
||||
const node = stack.pop()!;
|
||||
if (node.type === 'class_declaration') {
|
||||
if (node.type === 'class_declaration' || node.type === 'record_declaration') {
|
||||
const superclass = node.childForFieldName('superclass');
|
||||
if (superclass !== null) {
|
||||
for (const base of superclass.namedChildren) emitJavaInheritanceBase(out, base);
|
||||
|
|
|
|||
|
|
@ -1273,6 +1273,40 @@ describe('Java record method resolution (#2564)', () => {
|
|||
fs.rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
}, 60000);
|
||||
|
||||
it('links a Record implementor to its canonical Interface node (#2900)', async () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-java-record-heritage-'));
|
||||
try {
|
||||
writeFixtureRepo(root, {
|
||||
'RecordHeritage.java': `interface Named { String name(); }
|
||||
record User(String value) implements Named {
|
||||
public String name() { return value; }
|
||||
}
|
||||
class Reader {
|
||||
String read(Named value) { return value.name(); }
|
||||
}`,
|
||||
});
|
||||
|
||||
const linked = await runPipelineFromRepo(root, () => {});
|
||||
const implementsEdge = getRelationships(linked, 'IMPLEMENTS').find(
|
||||
(edge) => edge.source === 'User' && edge.target === 'Named',
|
||||
);
|
||||
const fanout = getRelationships(linked, 'CALLS').filter(
|
||||
(edge) =>
|
||||
edge.source === 'read' &&
|
||||
edge.target === 'name' &&
|
||||
edge.rel.reason === 'interface-dispatch',
|
||||
);
|
||||
|
||||
expect(implementsEdge?.sourceLabel).toBe('Record');
|
||||
expect(implementsEdge?.targetLabel).toBe('Interface');
|
||||
expect(fanout.map((edge) => `${edge.targetLabel}:${edge.targetFilePath}`)).toEqual([
|
||||
'Method:RecordHeritage.java',
|
||||
]);
|
||||
} finally {
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
}, 60000);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -29,6 +29,13 @@ function ctorRefs(src: string) {
|
|||
}));
|
||||
}
|
||||
|
||||
/** All synthesized inheritance references in `src`, reduced to lookup names. */
|
||||
function inheritanceRefs(src: string): string[] {
|
||||
return emitJavaScopeCaptures(src, 'C.java')
|
||||
.filter((m) => m['@reference.inherits'] !== undefined)
|
||||
.flatMap((m) => m['@reference.name']?.text ?? []);
|
||||
}
|
||||
|
||||
describe('emitJavaScopeCaptures — constructor reference names (F35 #1928)', () => {
|
||||
it('binds the simple name for an unqualified `new User()`', () => {
|
||||
const refs = ctorRefs(wrapExpr('new User()'));
|
||||
|
|
@ -82,6 +89,20 @@ describe('emitJavaScopeCaptures — constructor reference names (F35 #1928)', ()
|
|||
});
|
||||
});
|
||||
|
||||
describe('emitJavaScopeCaptures — record interface heritage (#2900)', () => {
|
||||
it('captures simple, generic, and qualified record interfaces by lookup name', () => {
|
||||
const refs = inheritanceRefs(
|
||||
'record User(int id) implements Named, Comparable<User>, audit.Auditable {}',
|
||||
);
|
||||
|
||||
expect(refs).toEqual(['Named', 'Comparable', 'Auditable']);
|
||||
});
|
||||
|
||||
it('does not widen enum heritage while adding record support', () => {
|
||||
expect(inheritanceRefs('enum Status implements Named { ACTIVE }')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('emitJavaScopeCaptures — explicit constructor invocations (F38 #1928)', () => {
|
||||
it('captures `super(...)` as a constructor ref to the superclass simple name', () => {
|
||||
const src = 'class C extends pkg.Base { C() { super(1, 2); } }';
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue