mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-23 00:41:36 +00:00
Three flat O(1) indexes + pure build functions over per-file artifacts. Contract-only; no runtime behavior change yet — consumers (#917 Registry lookups, #915 SCC finalize, #919 ScopeExtractor) wire in later. Each index follows the same shape: - build function: flat input list → frozen immutable index - public interface: readonly Map + get/has/size accessors - first-write-wins on id/filePath collisions (upstream bug signal) - pure, side-effect-free, safe to call repeatedly DefIndex — the global "what is this id?" lookup gitnexus-shared/src/scope-resolution/def-index.ts buildDefIndex(defs: readonly SymbolDefinition[]): DefIndex byId: ReadonlyMap<DefId, SymbolDefinition> Consumed by Registry.lookup (#917) to materialize DefId[] hits back to full SymbolDefinition records. ModuleScopeIndex — `filePath → moduleScopeId` for cross-file hops gitnexus-shared/src/scope-resolution/module-scope-index.ts buildModuleScopeIndex(entries): ModuleScopeIndex byFilePath: ReadonlyMap<string, ScopeId> Consumed by the SCC finalize link pass (#915) to resolve ImportEdge.targetFile to a concrete module scope in constant time. QualifiedNameIndex — cross-kind qualified-name fast path gitnexus-shared/src/scope-resolution/qualified-name-index.ts buildQualifiedNameIndex(defs: readonly SymbolDefinition[]): QualifiedNameIndex byQualifiedName: ReadonlyMap<string, readonly DefId[]> Returns DefId[] (not a single DefId) because partial classes, method overloads, and cross-kind collisions can legitimately share a qualifiedName. Callers filter by acceptedKinds at the lookup site. Consumed by Registry.lookup qualified fast path + resolveTypeRef dotted fallback (#916, #917). Barrel re-exports added to gitnexus-shared/src/index.ts so consumers import from 'gitnexus-shared' rather than deep paths. Tests (gitnexus/test/unit/scope-resolution/, 23 total): def-index.test.ts (6): empty, single def, multiple distinct, first-write-wins collision, missing id returns undefined, byId direct iteration module-scope-index.test.ts (6): empty, single entry, multiple files, first-write-wins on duplicate filePath, missing returns undefined, byFilePath direct iteration qualified-name-index.test.ts (11): empty, single qnamed def, partial classes accumulate, input-order preservation, qname separation, skip undefined/empty qname, pair dedup, cross-kind indexing, frozen-empty-array on miss, direct iteration Verification: - gitnexus-shared + gitnexus build clean (tsc + scripts/build.js) - test/unit/scope-resolution: 23/23 pass - model + shadow + scope-resolution combined: 129/129 pass - No runtime consumer wiring yet — indexes are standalone library functions that #915, #917, #919 will import when ready Depends on #910 (SymbolDefinition, DefId, ScopeId types — already on main). Unblocks #915 (finalize algorithm), #917 (Registry.lookup), #919 (ScopeExtractor materialization).
69 lines
2.4 KiB
TypeScript
69 lines
2.4 KiB
TypeScript
/**
|
|
* Unit tests for `buildDefIndex` / `DefIndex` (RFC #909 Ring 2 SHARED #913).
|
|
*
|
|
* Covers: build-from-list, O(1) lookup contract, first-write-wins on
|
|
* duplicate `nodeId`, readonly surface.
|
|
*/
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
import { buildDefIndex, type SymbolDefinition } from 'gitnexus-shared';
|
|
|
|
const makeDef = (overrides: Partial<SymbolDefinition> = {}): SymbolDefinition => ({
|
|
nodeId: 'def:test',
|
|
filePath: 'src/test.ts',
|
|
type: 'Method',
|
|
...overrides,
|
|
});
|
|
|
|
describe('buildDefIndex', () => {
|
|
it('builds an empty index from an empty input', () => {
|
|
const idx = buildDefIndex([]);
|
|
expect(idx.size).toBe(0);
|
|
expect(idx.get('anything')).toBeUndefined();
|
|
expect(idx.has('anything')).toBe(false);
|
|
});
|
|
|
|
it('stores a single def and round-trips by nodeId', () => {
|
|
const def = makeDef({ nodeId: 'def:User.save' });
|
|
const idx = buildDefIndex([def]);
|
|
expect(idx.size).toBe(1);
|
|
expect(idx.has('def:User.save')).toBe(true);
|
|
expect(idx.get('def:User.save')).toBe(def); // reference identity
|
|
});
|
|
|
|
it('stores multiple defs under their distinct ids', () => {
|
|
const a = makeDef({ nodeId: 'def:A' });
|
|
const b = makeDef({ nodeId: 'def:B' });
|
|
const c = makeDef({ nodeId: 'def:C' });
|
|
const idx = buildDefIndex([a, b, c]);
|
|
expect(idx.size).toBe(3);
|
|
expect(idx.get('def:A')).toBe(a);
|
|
expect(idx.get('def:B')).toBe(b);
|
|
expect(idx.get('def:C')).toBe(c);
|
|
});
|
|
|
|
it('first-write-wins on duplicate nodeId', () => {
|
|
const first = makeDef({ nodeId: 'def:dup', returnType: 'Original' });
|
|
const second = makeDef({ nodeId: 'def:dup', returnType: 'Shadow' });
|
|
const idx = buildDefIndex([first, second]);
|
|
expect(idx.size).toBe(1);
|
|
expect(idx.get('def:dup')).toBe(first);
|
|
expect(idx.get('def:dup')?.returnType).toBe('Original');
|
|
});
|
|
|
|
it("returns undefined for a missing id (doesn't throw)", () => {
|
|
const idx = buildDefIndex([makeDef({ nodeId: 'def:A' })]);
|
|
expect(idx.get('def:missing')).toBeUndefined();
|
|
expect(idx.has('def:missing')).toBe(false);
|
|
});
|
|
|
|
it('exposes byId as the underlying read-only Map for direct iteration', () => {
|
|
const a = makeDef({ nodeId: 'def:A' });
|
|
const b = makeDef({ nodeId: 'def:B' });
|
|
const idx = buildDefIndex([a, b]);
|
|
const entries = Array.from(idx.byId.entries())
|
|
.map(([id]) => id)
|
|
.sort();
|
|
expect(entries).toEqual(['def:A', 'def:B']);
|
|
});
|
|
});
|