fix(ingestion): prune exported block-local value symbols

This commit is contained in:
abhigyanpatwari 2026-06-07 05:20:27 +05:30
parent 36db1b3d2c
commit 6c82d84a2e
3 changed files with 84 additions and 2 deletions

View file

@ -24,8 +24,7 @@ const emptyStats = (skippedByEnv: boolean): LocalSymbolPruneStats => ({
const isLocalValueCandidate = (node: GraphNode): boolean => {
if (!LOCAL_VALUE_LABELS.has(node.label)) return false;
if (node.properties.scope !== 'block') return false;
return node.properties.isExported !== true;
return node.properties.scope === 'block';
};
const isFileDefinesEdgeToCandidate = (

View file

@ -0,0 +1,71 @@
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { afterEach, describe, expect, it } from 'vitest';
import { runPipelineFromRepo } from '../../src/core/ingestion/pipeline.js';
import { DIST_WORKER_URL, distWorkerExists } from '../helpers/worker-parse.js';
const describeIfWorkerBuilt = distWorkerExists() ? describe : describe.skip;
let tmpDirs: string[] = [];
const makeRepo = (source: string): string => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gn-local-prune-'));
tmpDirs.push(dir);
fs.writeFileSync(path.join(dir, 'sample.ts'), source, 'utf-8');
return dir;
};
const findNode = (
result: Awaited<ReturnType<typeof runPipelineFromRepo>>,
label: string,
name: string,
) => result.graph.nodes.find((node) => node.label === label && node.properties.name === name);
afterEach(() => {
for (const dir of tmpDirs) fs.rmSync(dir, { recursive: true, force: true });
tmpDirs = [];
});
describeIfWorkerBuilt('local value symbol pruning pipeline', () => {
it('prunes inert locals inside exported functions without losing resolved calls', async () => {
const repo = makeRepo(`
const MODULE_CONST = 1;
export const exportedHandler = () => MODULE_CONST;
export function run() {
const boring = 1;
const handler = () => boring;
const client = new Client();
client.send();
return handler();
}
class Client {
send() {}
}
`);
const result = await runPipelineFromRepo(repo, () => {}, {
skipGraphPhases: true,
workerPoolSize: 1,
workerUrlForTest: DIST_WORKER_URL,
});
expect(findNode(result, 'Const', 'boring')).toBeUndefined();
expect(findNode(result, 'Const', 'client')).toBeUndefined();
expect(findNode(result, 'Const', 'handler')).toBeUndefined();
expect(findNode(result, 'Const', 'MODULE_CONST')).toBeDefined();
expect(findNode(result, 'Const', 'exportedHandler')).toBeDefined();
expect(findNode(result, 'Function', 'handler')).toBeDefined();
const keepsResolvedClientCall = result.graph.relationships.some((rel) => {
if (rel.type !== 'CALLS') return false;
const source = result.graph.getNode(rel.sourceId);
const target = result.graph.getNode(rel.targetId);
return source?.properties.name === 'run' && target?.properties.name === 'send';
});
expect(keepsResolvedClientCall).toBe(true);
});
});

View file

@ -106,6 +106,18 @@ describe('pruneLocalValueSymbols', () => {
expect(graph.relationshipCount).toBe(2);
});
it('prunes block-scope value symbols even when parser metadata marks them exported', () => {
const graph = createKnowledgeGraph();
graph.addNode(fileNode());
graph.addNode(node('Const:tmp', 'Const', { scope: 'block', isExported: true }));
graph.addRelationship(rel('rel:def', 'file:src/app.ts', 'Const:tmp'));
const stats = pruneLocalValueSymbols(graph);
expect(stats.prunedNodes).toBe(1);
expect(graph.getNode('Const:tmp')).toBeUndefined();
});
it('keeps block-scope value symbols defined by explicit scope graph nodes', () => {
const graph = createKnowledgeGraph();
graph.addNode(fileNode());