mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-02 09:45:39 +08:00
* docs: record regression audit and validation * test: cover audited call and extraction regressions * test: cover audited calls and selector guards * test: cover audited calls and selector guards * test: cover audited calls and selector guards * test: cover audited calls and selector guards * test: cover audited calls and selector guards * fix: retain qualified call sites and restore language-specific getters * fix: resolve typed calls and bound store actions * fix: retain qualified call sites in the native kernel * docs: explain restored call and Steps coverage * test: isolate watchdog and cover imported store aliases * refactor: expose import lookup through resolver context * refactor: remove import resolver cycle * test: canonicalize Claude config temp paths * docs: record review fixes and large TypeScript timings * test: cover store cache edits and document verification * perf: cache per-file store eligibility * fix: observe large benchmarks without a wall timeout * docs: explain completed indexing benchmarks docs: explain completed indexing benchmarks
87 lines
3.6 KiB
TypeScript
87 lines
3.6 KiB
TypeScript
import { afterEach, describe, expect, it } from 'vitest';
|
|
import * as fs from 'node:fs';
|
|
import * as os from 'node:os';
|
|
import * as path from 'node:path';
|
|
import { CodeGraph } from '../src';
|
|
|
|
const projects: { dir: string; cg: CodeGraph }[] = [];
|
|
afterEach(() => {
|
|
for (const { dir, cg } of projects.splice(0)) {
|
|
cg.close();
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
function consumer(active: boolean): string {
|
|
return `import { useStore as current } from './store';
|
|
export function run() {
|
|
const { reset } = ${active ? 'current.getState()' : 'external()'};
|
|
reset();
|
|
reset();
|
|
}
|
|
export function effects(client: any) {
|
|
client.user.create();
|
|
client?.user?.create();
|
|
}
|
|
`;
|
|
}
|
|
|
|
async function project(active: boolean) {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-store-cache-'));
|
|
fs.writeFileSync(path.join(dir, 'store.ts'), `import { create } from 'zustand';
|
|
export const useStore = create((set) => ({ reset: () => set({}) }));
|
|
`);
|
|
fs.writeFileSync(path.join(dir, 'decoy.ts'), 'export function reset() { return 99; }');
|
|
fs.writeFileSync(path.join(dir, 'consumer.ts'), consumer(active));
|
|
const cg = CodeGraph.initSync(dir);
|
|
projects.push({ dir, cg });
|
|
const result = await cg.indexAll();
|
|
expect(result.success).toBe(true);
|
|
expect(result.filesErrored).toBe(0);
|
|
return { dir, cg };
|
|
}
|
|
|
|
function assertBindings(cg: CodeGraph, active: boolean) {
|
|
const functions = cg.getNodesByKind('function');
|
|
const run = functions.find(n => n.name === 'run' && n.filePath === 'consumer.ts')!;
|
|
const action = functions.find(n => n.name === 'reset' && n.filePath === 'store.ts')!;
|
|
const decoy = functions.find(n => n.name === 'reset' && n.filePath === 'decoy.ts')!;
|
|
const targets = cg.getOutgoingEdges(run.id).filter(e => e.kind === 'calls').map(e => e.target);
|
|
expect(targets.includes(action.id)).toBe(active);
|
|
expect(targets).not.toContain(decoy.id);
|
|
const pendingActions = cg.getUnresolvedReferencesFrom(run.id).filter(r => r.referenceName === 'reset');
|
|
expect(pendingActions).toHaveLength(active ? 0 : 2);
|
|
|
|
// Eligibility must not remove untyped qualified call-site evidence, including
|
|
// repeated/optional chains, or turn it into a guessed edge.
|
|
const effects = functions.find(n => n.name === 'effects' && n.filePath === 'consumer.ts')!;
|
|
expect(cg.getOutgoingEdges(effects.id).filter(e => e.kind === 'calls')).toEqual([]);
|
|
expect(cg.getUnresolvedReferencesFrom(effects.id).filter(r => r.referenceKind === 'calls')
|
|
.map(r => [r.referenceName, r.line, r.column])).toEqual([
|
|
['client.user.create', 8, 2], ['client.user.create', 9, 2],
|
|
]);
|
|
}
|
|
|
|
describe('store eligibility cache across edits and resolver contexts', () => {
|
|
it.each([false, true])('sync refreshes eligibility starting with getState=%s', async (initial) => {
|
|
const { dir, cg } = await project(initial);
|
|
assertBindings(cg, initial);
|
|
// Reuse the same CodeGraph/resolver and path in both directions. A cached
|
|
// negative must not mask a new store binding, and removing it must remove
|
|
// both action edges while preserving unresolved call-site evidence.
|
|
for (const active of [!initial, initial]) {
|
|
fs.writeFileSync(path.join(dir, 'consumer.ts'), consumer(active));
|
|
const result = await cg.sync();
|
|
expect(result.filesModified).toBe(1);
|
|
assertBindings(cg, active);
|
|
}
|
|
}, 60000);
|
|
|
|
it('does not share eligibility between projects with the same relative file path', async () => {
|
|
const absent = await project(false);
|
|
const present = await project(true);
|
|
assertBindings(absent.cg, false);
|
|
assertBindings(present.cg, true);
|
|
}, 60000);
|
|
});
|