fix(SM-15): address PR #763 third-pass review findings

1. Update stale dispose() JSDoc — remove forward-reference to Phase 9
   wiring that is now complete; document actual consumers.

2. Add processAssignmentsFromExtracted Phase 9 unit test — verifies the
   accumulator fallback produces ACCESSES write edges when the SymbolTable
   has no returnType for the callee.
This commit is contained in:
Gergo Magyar 2026-04-10 10:16:37 +01:00
parent 00e62da0f8
commit 30677d4e9f
2 changed files with 63 additions and 6 deletions

View file

@ -276,12 +276,11 @@ export class BindingAccumulator {
* **after** `finalize()`, subsequent `appendFile` calls throw the existing
* "finalized" error.
*
* Lifecycle note: the pipeline disposes the accumulator after the
* ExportedTypeMap enrichment loop consumes its file-scope entries, so
* the heap is released before Phase 14 (`runCrossFileBindingPropagation`)
* and `runGraphAnalysisPhases` begin their long-running work. When Phase 9
* wires a consumer into that stage, the dispose call should move later in
* the pipeline or be removed entirely.
* Lifecycle note: the pipeline disposes the accumulator after both Phase 9
* consumers (`processCallsFromExtracted`, `processAssignmentsFromExtracted`)
* and the ExportedTypeMap enrichment loop have completed, so the heap is
* released before Phase 14 (`runCrossFileBindingPropagation`) and
* `runGraphAnalysisPhases` begin their long-running work.
*/
dispose(): void {
this._allByFile.clear();

View file

@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
import {
processCalls,
processCallsFromExtracted,
processAssignmentsFromExtracted,
seedCrossFileReceiverTypes,
extractConsumerAccessedKeys,
processNextjsFetchRoutes,
@ -16,6 +17,7 @@ import {
import { createKnowledgeGraph } from '../../src/core/graph/graph.js';
import { BindingAccumulator } from '../../src/core/ingestion/binding-accumulator.js';
import type {
ExtractedAssignment,
ExtractedCall,
ExtractedFetchCall,
ExtractedHeritage,
@ -2492,3 +2494,59 @@ describe('processCalls — D0 MRO fast path (SM-10)', () => {
expect(userSave).toBeUndefined();
});
});
// ---- processAssignmentsFromExtracted: Phase 9 accumulator fallback ----
describe('processAssignmentsFromExtracted', () => {
let graph: ReturnType<typeof createKnowledgeGraph>;
let ctx: ResolutionContext;
beforeEach(() => {
graph = createKnowledgeGraph();
ctx = createResolutionContext();
});
it('Phase 9: accumulator fallback resolves receiver type for ACCESSES write edge', () => {
// getUser is in the SymbolTable WITHOUT a returnType. The accumulator
// carries getUser → User from the source file. The constructor binding
// binds x = getUser(). The assignment x.address = value should produce
// an ACCESSES write edge to User.address via the accumulator fallback.
ctx.symbols.add('src/api.ts', 'getUser', 'Function:src/api.ts:getUser', 'Function');
ctx.symbols.add('src/models.ts', 'User', 'Class:src/models.ts:User', 'Class');
ctx.symbols.add('src/models.ts', 'address', 'Property:src/models.ts:address', 'Property', {
ownerId: 'Class:src/models.ts:User',
});
ctx.importMap.set('src/consumer.ts', new Set(['src/api.ts', 'src/models.ts']));
ctx.namedImportMap.set(
'src/consumer.ts',
new Map([['getUser', { sourcePath: 'src/api.ts', exportedName: 'getUser' }]]),
);
const acc = new BindingAccumulator();
acc.appendFile('src/api.ts', [{ scope: '', varName: 'getUser', typeName: 'User' }]);
const constructorBindings: FileConstructorBindings[] = [
{
filePath: 'src/consumer.ts',
bindings: [{ scope: 'main@0', varName: 'x', calleeName: 'getUser' }],
},
];
const assignments: ExtractedAssignment[] = [
{
filePath: 'src/consumer.ts',
sourceId: 'Function:src/consumer.ts:main',
receiverText: 'x',
propertyName: 'address',
},
];
processAssignmentsFromExtracted(graph, assignments, ctx, constructorBindings, acc);
const accesses = graph.relationships.filter(
(r) => r.type === 'ACCESSES' && r.reason === 'write',
);
expect(accesses).toHaveLength(1);
expect(accesses[0].targetId).toBe('Property:src/models.ts:address');
});
});