feat(type-env): add flush() method to TypeEnvironment

Adds flush(filePath, accumulator) to the TypeEnvironment interface and
buildTypeEnv return object, draining all scoped bindings into a
BindingAccumulator. Adds 4 unit tests covering file-scope, function-scope,
empty env, and multi-file accumulation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
abhigyanpatwari 2026-04-09 02:12:27 +05:30
parent 3111f4dcd3
commit 56e2e7f520
2 changed files with 78 additions and 0 deletions

View file

@ -8,6 +8,7 @@ import { CALL_EXPRESSION_TYPES } from './utils/call-analysis.js';
import { SupportedLanguages } from 'gitnexus-shared';
import { TYPED_PARAMETER_TYPES } from './type-extractors/shared.js';
import { getProvider } from './languages/index.js';
import type { BindingAccumulator, BindingEntry } from './binding-accumulator.js';
import type {
ClassNameLookup,
ReturnTypeLookup,
@ -73,6 +74,9 @@ export interface TypeEnvironment {
* Populated when a variable has BOTH a declared base type AND a more specific
* constructor type (e.g., `Animal a = new Dog()` → key maps to 'Dog'). */
readonly constructorTypeMap: ReadonlyMap<string, string>;
/** Drain all scoped bindings into a BindingAccumulator.
* Called once per file at end of processing. Skips empty scopes. */
flush(filePath: string, accumulator: BindingAccumulator): void;
}
/**
@ -1245,6 +1249,17 @@ export const buildTypeEnv = (
fileScope: () => env.get(FILE_SCOPE) ?? EMPTY_FILE_SCOPE,
allScopes: () => env as ReadonlyMap<string, ReadonlyMap<string, string>>,
constructorTypeMap,
flush(filePath: string, accumulator: BindingAccumulator): void {
const entries: BindingEntry[] = [];
for (const [scope, scopeMap] of env) {
for (const [varName, typeName] of scopeMap) {
entries.push({ scope, varName, typeName });
}
}
if (entries.length > 0) {
accumulator.appendFile(filePath, entries);
}
},
};
};

View file

@ -1,5 +1,6 @@
import { describe, it, expect, vi } from 'vitest';
import { buildTypeEnv, type TypeEnvironment } from '../../src/core/ingestion/type-env.js';
import { BindingAccumulator } from '../../src/core/ingestion/binding-accumulator.js';
import {
createSymbolTable,
type SymbolDefinition,
@ -5859,4 +5860,66 @@ function process() {
expect(typeEnv.lookup('c', validateCall)).toBe('Config');
});
});
describe('flush', () => {
it('flushes file-scope bindings into accumulator', () => {
const code = `const user: User = getUser();\nconst count: number = 0;`;
const tree = parse(code, TypeScript.typescript);
const typeEnv = buildTypeEnv(tree, 'typescript');
const acc = new BindingAccumulator();
typeEnv.flush('/src/test.ts', acc);
const entries = acc.getFile('/src/test.ts');
expect(entries).toBeDefined();
const userEntry = entries!.find(e => e.varName === 'user');
expect(userEntry).toBeDefined();
expect(userEntry!.typeName).toBe('User');
expect(userEntry!.scope).toBe('');
});
it('flushes function-scoped bindings into accumulator', () => {
const code = `function process() {\n const result: Response = fetch();\n}`;
const tree = parse(code, TypeScript.typescript);
const typeEnv = buildTypeEnv(tree, 'typescript');
const acc = new BindingAccumulator();
typeEnv.flush('/src/test.ts', acc);
const entries = acc.getFile('/src/test.ts');
expect(entries).toBeDefined();
const resultEntry = entries!.find(e => e.varName === 'result');
expect(resultEntry).toBeDefined();
expect(resultEntry!.typeName).toBe('Response');
expect(resultEntry!.scope).not.toBe('');
});
it('flushes nothing for an empty TypeEnv', () => {
const code = `// empty file`;
const tree = parse(code, TypeScript.typescript);
const typeEnv = buildTypeEnv(tree, 'typescript');
const acc = new BindingAccumulator();
typeEnv.flush('/src/empty.ts', acc);
expect(acc.getFile('/src/empty.ts')).toBeUndefined();
});
it('multiple files flush into same accumulator', () => {
const code1 = `const a: A = makeA();`;
const code2 = `const b: B = makeB();`;
const tree1 = parse(code1, TypeScript.typescript);
const tree2 = parse(code2, TypeScript.typescript);
const typeEnv1 = buildTypeEnv(tree1, 'typescript');
const typeEnv2 = buildTypeEnv(tree2, 'typescript');
const acc = new BindingAccumulator();
typeEnv1.flush('/src/a.ts', acc);
typeEnv2.flush('/src/b.ts', acc);
expect(acc.fileCount).toBe(2);
expect(acc.getFile('/src/a.ts')).toBeDefined();
expect(acc.getFile('/src/b.ts')).toBeDefined();
});
});
});