GitNexus/gitnexus/test/unit/import-processor.test.ts
Gergo Magyar 2c17a4642c refactor: unify language dispatch with compile-time exhaustive tables
Replace the 120-line if-chain in resolveLanguageImport() and the 7-branch
dispatch in extractNamedBindings() with per-language dispatch tables using
`satisfies Record<SupportedLanguages, T>` for compile-time exhaustiveness.

Key changes:
- New import-resolution.ts: buildImportResolvers factory + namedBindingExtractors
  table + preprocessImportPath with control character rejection
- Extracted loadImportConfigs(), createImportEdgeHelpers(), getLabelFromCaptures()
  to eliminate duplication across import-processor.ts and parse-worker.ts
- Added isCppDuplicateClassFunction and getLabelFromCaptures shared helpers
- Migrated ENTRY_POINT_PATTERNS and AST_FRAMEWORK_PATTERNS_BY_LANGUAGE to
  satisfies Record<SupportedLanguages, T> with compile-time exhaustiveness
- Added Kotlin entry-point patterns (Android lifecycle, Ktor, MVVM)
- Expanded framework detection: Go (Gin/Echo/Fiber/gRPC), Rust (Actix/Axum/
  Rocket/Tokio), C++ (Qt), Swift (UIKit/SwiftUI/Vapor), Ruby (Rails/Sinatra)
- Fixed Go cmd/ entry-point detection operator precedence bug
- Added EMPTY_INDEX frozen sentinel for type-safe memory cleanup
- Unified ImportResolutionContext (suffixIndex->index, removed unused dispose())
- Extracted NamedBinding interface (replaced 16 inline occurrences)
- Replaced any with SyntaxNode on all tree-sitter node parameters
- Added contributor checklist to SupportedLanguages enum

14 files changed, +318/-550 (net -232 lines). All 3550+ tests pass.
2026-03-21 11:48:11 +00:00

87 lines
2.8 KiB
TypeScript

import { describe, it, expect, beforeEach } from 'vitest';
import { buildImportResolutionContext, type ImportResolutionContext } from '../../src/core/ingestion/import-processor.js';
import { createResolutionContext } from '../../src/core/ingestion/resolution-context.js';
describe('ResolutionContext.importMap', () => {
it('creates an empty Map', () => {
const map = createResolutionContext().importMap;
expect(map).toBeInstanceOf(Map);
expect(map.size).toBe(0);
});
it('can be used to store import relationships', () => {
const map = createResolutionContext().importMap;
map.set('src/index.ts', new Set(['src/utils.ts', 'src/types.ts']));
expect(map.get('src/index.ts')!.size).toBe(2);
expect(map.get('src/index.ts')!.has('src/utils.ts')).toBe(true);
});
});
describe('buildImportResolutionContext', () => {
let ctx: ImportResolutionContext;
const testPaths = [
'src/index.ts',
'src/utils.ts',
'src/components/Button.tsx',
'src/lib/helpers.ts',
];
beforeEach(() => {
ctx = buildImportResolutionContext(testPaths);
});
it('creates a Set of all file paths', () => {
expect(ctx.allFilePaths).toBeInstanceOf(Set);
expect(ctx.allFilePaths.size).toBe(4);
expect(ctx.allFilePaths.has('src/index.ts')).toBe(true);
});
it('stores the original file list', () => {
expect(ctx.allFileList).toBe(testPaths);
});
it('creates normalized file list with forward slashes', () => {
const winPaths = ['src\\index.ts', 'src\\utils.ts'];
const winCtx = buildImportResolutionContext(winPaths);
expect(winCtx.normalizedFileList[0]).toBe('src/index.ts');
expect(winCtx.normalizedFileList[1]).toBe('src/utils.ts');
});
it('creates a suffix index for O(1) lookups', () => {
expect(ctx.index).toBeDefined();
expect(typeof ctx.index.get).toBe('function');
});
it('initializes empty resolve cache', () => {
expect(ctx.resolveCache).toBeInstanceOf(Map);
expect(ctx.resolveCache.size).toBe(0);
});
it('handles empty paths array', () => {
const emptyCtx = buildImportResolutionContext([]);
expect(emptyCtx.allFilePaths.size).toBe(0);
expect(emptyCtx.allFileList).toHaveLength(0);
});
describe('suffix index', () => {
it('resolves file by suffix', () => {
const result = ctx.index.get('utils.ts');
expect(result).toBeDefined();
});
it('resolves file by full path', () => {
const result = ctx.index.get('src/index.ts');
expect(result).toBeDefined();
});
it('resolves nested component path', () => {
const result = ctx.index.get('components/Button.tsx');
expect(result).toBeDefined();
});
it('returns undefined for non-existent suffix', () => {
const result = ctx.index.get('nonexistent.ts');
expect(result).toBeUndefined();
});
});
});