fix(extraction): index C++ pure virtual methods as nodes (#1727) (#1758)

Pure-virtual declarations (`virtual int read(int key) = 0;`) parse as
field_declaration, not function_definition, so they minted no method node —
calls through an abstract base and cpp-override synthesis had nothing to
attach to. Mirror Java interface methods: mint the node (TS + kernel), mark
isAbstract, and cover with extraction/e2e/parity fixtures.

Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com>
This commit is contained in:
Colby Mchenry
2026-09-08 02:04:12 -05:00
committed by GitHub
co-authored by Colby McHenry
parent 8df9ecac9d
commit 2f1a99d34c
7 changed files with 240 additions and 6 deletions
+55
View File
@@ -353,6 +353,61 @@ describe('C++ end-to-end — virtual override synthesis', () => {
cg.close();
});
it('indexes pure-virtual base methods and bridges overrides (#1727)', async () => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-cpp-pure-'));
fs.writeFileSync(
path.join(tmpDir, 'store.cc'),
'class Store {\n' +
'public:\n' +
' virtual ~Store() {}\n' +
' virtual int read(int key) = 0;\n' +
'};\n' +
'class DiskStore : public Store {\n' +
'public:\n' +
' int read(int key) override { return key + 1; }\n' +
'};\n' +
'class MemStore : public Store {\n' +
'public:\n' +
' int read(int key) override { return key + 2; }\n' +
'};\n' +
'int fetch(Store* s, int k) {\n' +
' return s->read(k);\n' +
'}\n'
);
const cg = CodeGraph.initSync(tmpDir);
await cg.indexAll();
const storeRead = cg
.getNodesByKind('method')
.find((n) => n.qualifiedName === 'Store::read');
expect(storeRead, 'Store::read pure virtual must be a method node').toBeDefined();
expect(storeRead!.isAbstract).toBe(true);
const diskRead = cg
.getNodesByKind('method')
.find((n) => n.qualifiedName === 'DiskStore::read');
const memRead = cg
.getNodesByKind('method')
.find((n) => n.qualifiedName === 'MemStore::read');
expect(diskRead).toBeDefined();
expect(memRead).toBeDefined();
// cpp-override synthesis: base pure virtual → each override
const out = cg.getOutgoingEdges(storeRead!.id).filter((e) => e.kind === 'calls');
const targets = out.map((e) => e.target);
expect(targets).toContain(diskRead!.id);
expect(targets).toContain(memRead!.id);
// Call through abstract base resolves onto Store::read
const fetch = cg.getNodesByKind('function').find((n) => n.name === 'fetch');
expect(fetch).toBeDefined();
const callees = cg.getCallees(fetch!.id).map((c) => c.node.qualifiedName);
expect(callees).toContain('Store::read');
cg.close();
});
});
describe('Java end-to-end — field-injected bean trace (issue #389)', () => {