mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
refactor(ingestion): strengthen import-resolver tests and document Vue config intent
Address non-blocking follow-ups from PR #886 review: - Add inline comment to vueImportConfig explaining intentional language: Vue / TypeScript-strategy mismatch (Vue SFCs are preprocessed into TS upstream of import resolution). - Replace 11 tautological typeof === 'function' assertions with behavioral tests for goPackageStrategy, kotlinJvmStrategy, and csharpNamespaceStrategy, including full-chain strategy-order guards via createImportResolver(config). - Apply prettier formatting to sibling configs touched during factory introduction. Test: 37 passed (previously 26), tsc --noEmit clean.
This commit is contained in:
parent
1c5e12e594
commit
f4be87fb8d
12 changed files with 134 additions and 102 deletions
|
|
@ -9,11 +9,7 @@ import { createStandardStrategy } from '../standard.js';
|
|||
import { resolveCSharpImportInternal, resolveCSharpNamespaceDir } from '../csharp.js';
|
||||
|
||||
/** C# namespace-based resolution strategy via .csproj configs. */
|
||||
export const csharpNamespaceStrategy: ImportResolverStrategy = (
|
||||
rawImportPath,
|
||||
_filePath,
|
||||
ctx,
|
||||
) => {
|
||||
export const csharpNamespaceStrategy: ImportResolverStrategy = (rawImportPath, _filePath, ctx) => {
|
||||
const csharpConfigs = ctx.configs.csharpConfigs;
|
||||
if (csharpConfigs.length > 0) {
|
||||
const resolvedFiles = resolveCSharpImportInternal(
|
||||
|
|
|
|||
|
|
@ -12,11 +12,7 @@ import { resolveStandard } from '../standard.js';
|
|||
* Absorbs dart: SDK imports and external packages (returns empty result to stop chain).
|
||||
* Returns null for relative imports to let the next strategy handle them.
|
||||
*/
|
||||
export const dartPackageStrategy: ImportResolverStrategy = (
|
||||
rawImportPath,
|
||||
_filePath,
|
||||
ctx,
|
||||
) => {
|
||||
export const dartPackageStrategy: ImportResolverStrategy = (rawImportPath, _filePath, ctx) => {
|
||||
// Strip surrounding quotes from configurable_uri capture
|
||||
const stripped = rawImportPath.replace(/^['"]|['"]$/g, '');
|
||||
|
||||
|
|
@ -50,11 +46,7 @@ export const dartPackageStrategy: ImportResolverStrategy = (
|
|||
* Dart relative import strategy — prepends "./" for bare relative paths,
|
||||
* then delegates to standard resolution.
|
||||
*/
|
||||
export const dartRelativeStrategy: ImportResolverStrategy = (
|
||||
rawImportPath,
|
||||
filePath,
|
||||
ctx,
|
||||
) => {
|
||||
export const dartRelativeStrategy: ImportResolverStrategy = (rawImportPath, filePath, ctx) => {
|
||||
const stripped = rawImportPath.replace(/^['"]|['"]$/g, '');
|
||||
const relPath = stripped.startsWith('.') ? stripped : './' + stripped;
|
||||
return resolveStandard(relPath, filePath, ctx, SupportedLanguages.Dart);
|
||||
|
|
|
|||
|
|
@ -9,11 +9,7 @@ import { createStandardStrategy } from '../standard.js';
|
|||
import { resolveGoPackageDir, resolveGoPackage } from '../go.js';
|
||||
|
||||
/** Go-specific package resolution strategy — resolves go.mod-based package imports. */
|
||||
export const goPackageStrategy: ImportResolverStrategy = (
|
||||
rawImportPath,
|
||||
_filePath,
|
||||
ctx,
|
||||
) => {
|
||||
export const goPackageStrategy: ImportResolverStrategy = (rawImportPath, _filePath, ctx) => {
|
||||
const goModule = ctx.configs.goModule;
|
||||
if (goModule && rawImportPath.startsWith(goModule.modulePath)) {
|
||||
const pkgSuffix = resolveGoPackageDir(rawImportPath, goModule);
|
||||
|
|
|
|||
|
|
@ -9,11 +9,7 @@ import { createStandardStrategy } from '../standard.js';
|
|||
import { resolveJvmWildcard, resolveJvmMemberImport, KOTLIN_EXTENSIONS } from '../jvm.js';
|
||||
|
||||
/** Java JVM resolution strategy — wildcard and member import resolution. */
|
||||
export const javaJvmStrategy: ImportResolverStrategy = (
|
||||
rawImportPath,
|
||||
_filePath,
|
||||
ctx,
|
||||
) => {
|
||||
export const javaJvmStrategy: ImportResolverStrategy = (rawImportPath, _filePath, ctx) => {
|
||||
if (rawImportPath.endsWith('.*')) {
|
||||
const matchedFiles = resolveJvmWildcard(
|
||||
rawImportPath,
|
||||
|
|
@ -39,11 +35,7 @@ export const javaJvmStrategy: ImportResolverStrategy = (
|
|||
/**
|
||||
* Kotlin JVM resolution strategy — wildcard/member with Java-interop + top-level function imports.
|
||||
*/
|
||||
export const kotlinJvmStrategy: ImportResolverStrategy = (
|
||||
rawImportPath,
|
||||
_filePath,
|
||||
ctx,
|
||||
) => {
|
||||
export const kotlinJvmStrategy: ImportResolverStrategy = (rawImportPath, _filePath, ctx) => {
|
||||
if (rawImportPath.endsWith('.*')) {
|
||||
const matchedFiles = resolveJvmWildcard(
|
||||
rawImportPath,
|
||||
|
|
|
|||
|
|
@ -8,11 +8,7 @@ import type { ImportResolutionConfig, ImportResolverStrategy } from '../types.js
|
|||
import { resolvePhpImportInternal } from '../php.js';
|
||||
|
||||
/** PHP PSR-4 resolution strategy via composer.json autoload mappings. */
|
||||
export const phpPsr4Strategy: ImportResolverStrategy = (
|
||||
rawImportPath,
|
||||
_filePath,
|
||||
ctx,
|
||||
) => {
|
||||
export const phpPsr4Strategy: ImportResolverStrategy = (rawImportPath, _filePath, ctx) => {
|
||||
const resolved = resolvePhpImportInternal(
|
||||
rawImportPath,
|
||||
ctx.configs.composerConfig,
|
||||
|
|
|
|||
|
|
@ -13,11 +13,7 @@ import { resolvePythonImportInternal } from '../python.js';
|
|||
* Returns null to continue chain for non-relative imports.
|
||||
* Absorbs unresolved relative imports (returns empty result to stop the chain).
|
||||
*/
|
||||
export const pythonImportStrategy: ImportResolverStrategy = (
|
||||
rawImportPath,
|
||||
filePath,
|
||||
ctx,
|
||||
) => {
|
||||
export const pythonImportStrategy: ImportResolverStrategy = (rawImportPath, filePath, ctx) => {
|
||||
const resolved = resolvePythonImportInternal(filePath, rawImportPath, ctx.allFilePaths);
|
||||
if (resolved) {
|
||||
ctx.resolveCache.set(`${filePath}::${rawImportPath}`, resolved);
|
||||
|
|
|
|||
|
|
@ -8,11 +8,7 @@ import type { ImportResolutionConfig, ImportResolverStrategy } from '../types.js
|
|||
import { suffixResolve } from '../utils.js';
|
||||
|
||||
/** Ruby require/require_relative resolution strategy. */
|
||||
export const rubyRequireStrategy: ImportResolverStrategy = (
|
||||
rawImportPath,
|
||||
_filePath,
|
||||
ctx,
|
||||
) => {
|
||||
export const rubyRequireStrategy: ImportResolverStrategy = (rawImportPath, _filePath, ctx) => {
|
||||
const pathParts = rawImportPath.replace(/^\.\//, '').split('/').filter(Boolean);
|
||||
const resolved = suffixResolve(pathParts, ctx.normalizedFileList, ctx.allFileList, ctx.index);
|
||||
return resolved ? { kind: 'files', files: [resolved] } : null;
|
||||
|
|
|
|||
|
|
@ -9,11 +9,7 @@ import { createStandardStrategy } from '../standard.js';
|
|||
import { resolveRustImportInternal } from '../rust.js';
|
||||
|
||||
/** Rust module resolution strategy — handles grouped imports and crate/super/self paths. */
|
||||
export const rustModuleStrategy: ImportResolverStrategy = (
|
||||
rawImportPath,
|
||||
filePath,
|
||||
ctx,
|
||||
) => {
|
||||
export const rustModuleStrategy: ImportResolverStrategy = (rawImportPath, filePath, ctx) => {
|
||||
// Top-level grouped: use {crate::a, crate::b}
|
||||
if (rawImportPath.startsWith('{') && rawImportPath.endsWith('}')) {
|
||||
const inner = rawImportPath.slice(1, -1);
|
||||
|
|
|
|||
|
|
@ -7,11 +7,7 @@ import { SupportedLanguages } from 'gitnexus-shared';
|
|||
import type { ImportResolutionConfig, ImportResolverStrategy } from '../types.js';
|
||||
|
||||
/** Swift Package.swift target map resolution strategy. */
|
||||
export const swiftPackageStrategy: ImportResolverStrategy = (
|
||||
rawImportPath,
|
||||
_filePath,
|
||||
ctx,
|
||||
) => {
|
||||
export const swiftPackageStrategy: ImportResolverStrategy = (rawImportPath, _filePath, ctx) => {
|
||||
const swiftPackageConfig = ctx.configs.swiftPackageConfig;
|
||||
if (swiftPackageConfig) {
|
||||
const targetDir = swiftPackageConfig.targets.get(rawImportPath);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ export const javascriptImportConfig: ImportResolutionConfig = {
|
|||
strategies: [createStandardStrategy(SupportedLanguages.JavaScript)],
|
||||
};
|
||||
|
||||
// Vue SFCs are preprocessed into TypeScript upstream of import resolution,
|
||||
// so the resolver intentionally runs as TypeScript. `language: Vue` here is
|
||||
// documentation-only metadata (see `ImportResolutionConfig.language` JSDoc
|
||||
// and ARCHITECTURE.md §Vue); it is not consumed by `createImportResolver`.
|
||||
export const vueImportConfig: ImportResolutionConfig = {
|
||||
language: SupportedLanguages.Vue,
|
||||
strategies: [createStandardStrategy(SupportedLanguages.TypeScript)],
|
||||
|
|
|
|||
|
|
@ -61,11 +61,7 @@ describe('dartPackageStrategy', () => {
|
|||
describe('package: imports', () => {
|
||||
it('resolves local package import to lib/', () => {
|
||||
const ctx = makeCtx(['lib/models/user.dart', 'lib/main.dart']);
|
||||
const result = dartPackageStrategy(
|
||||
"'package:my_app/models/user.dart'",
|
||||
'lib/main.dart',
|
||||
ctx,
|
||||
);
|
||||
const result = dartPackageStrategy("'package:my_app/models/user.dart'", 'lib/main.dart', ctx);
|
||||
expect(result).toEqual({ kind: 'files', files: ['lib/models/user.dart'] });
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -21,20 +21,10 @@ import { buildSuffixIndex } from '../../src/core/ingestion/import-resolvers/util
|
|||
|
||||
// ── Per-language strategy imports (from config files) ──────────────────
|
||||
import { goPackageStrategy } from '../../src/core/ingestion/import-resolvers/configs/go.js';
|
||||
import {
|
||||
javaJvmStrategy,
|
||||
kotlinJvmStrategy,
|
||||
} from '../../src/core/ingestion/import-resolvers/configs/jvm.js';
|
||||
import { rustModuleStrategy } from '../../src/core/ingestion/import-resolvers/configs/rust.js';
|
||||
import { kotlinJvmStrategy } from '../../src/core/ingestion/import-resolvers/configs/jvm.js';
|
||||
import { pythonImportStrategy } from '../../src/core/ingestion/import-resolvers/configs/python.js';
|
||||
import { csharpNamespaceStrategy } from '../../src/core/ingestion/import-resolvers/configs/csharp.js';
|
||||
import { phpPsr4Strategy } from '../../src/core/ingestion/import-resolvers/configs/php.js';
|
||||
import { swiftPackageStrategy } from '../../src/core/ingestion/import-resolvers/configs/swift.js';
|
||||
import {
|
||||
dartPackageStrategy,
|
||||
dartRelativeStrategy,
|
||||
} from '../../src/core/ingestion/import-resolvers/configs/dart.js';
|
||||
import { rubyRequireStrategy } from '../../src/core/ingestion/import-resolvers/configs/ruby.js';
|
||||
import { dartPackageStrategy } from '../../src/core/ingestion/import-resolvers/configs/dart.js';
|
||||
|
||||
// ── Per-language config imports ────────────────────────────────────────
|
||||
import {
|
||||
|
|
@ -114,7 +104,10 @@ describe('createImportResolver', () => {
|
|||
|
||||
it('stops chain when strategy returns result with empty files (absorbing sentinel)', () => {
|
||||
const absorber: ImportResolverStrategy = () => ({ kind: 'files', files: [] });
|
||||
const shouldNotRun: ImportResolverStrategy = () => ({ kind: 'files', files: ['should-not.ts'] });
|
||||
const shouldNotRun: ImportResolverStrategy = () => ({
|
||||
kind: 'files',
|
||||
files: ['should-not.ts'],
|
||||
});
|
||||
|
||||
const resolver = createImportResolver({
|
||||
language: SupportedLanguages.TypeScript,
|
||||
|
|
@ -145,52 +138,135 @@ describe('createImportResolver', () => {
|
|||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Per-language strategies — exported and callable
|
||||
// Per-language strategies — behavioral coverage
|
||||
//
|
||||
// The previous `typeof strategy === 'function'` assertions were tautological:
|
||||
// TypeScript's `ImportResolverStrategy` type enforces the function shape at
|
||||
// compile time, so those tests could never fail. Python and Dart already have
|
||||
// deep behavioral tests below; Go / Kotlin / C# are covered here. The
|
||||
// remaining strategies (Java, Rust, PHP, Swift, Ruby) are exercised via the
|
||||
// per-language `createImportResolver(config)` smoke tests and the factory
|
||||
// behavior suite, which together verify exports, wiring, and composition.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('per-language strategy exports', () => {
|
||||
it('goPackageStrategy is a function', () => {
|
||||
expect(typeof goPackageStrategy).toBe('function');
|
||||
describe('goPackageStrategy', () => {
|
||||
it('resolves go.mod package imports to a package result with dirSuffix', () => {
|
||||
const files = ['cmd/server/main.go', 'cmd/server/handler.go'];
|
||||
const ctx = makeCtx(files);
|
||||
ctx.configs.goModule = { modulePath: 'example.com/app' };
|
||||
|
||||
const result = goPackageStrategy('example.com/app/cmd/server', 'main.go', ctx);
|
||||
// `kind: 'package'` + `dirSuffix` is unique to goPackageStrategy — the
|
||||
// standard strategy always returns `kind: 'files'`. Asserting this shape
|
||||
// makes config-level strategy ordering observable via the full-chain test
|
||||
// in `goImportConfig` below.
|
||||
expect(result?.kind).toBe('package');
|
||||
expect(result?.files).toEqual(expect.arrayContaining(files));
|
||||
if (result?.kind === 'package') {
|
||||
expect(result.dirSuffix).toContain('cmd/server');
|
||||
}
|
||||
});
|
||||
|
||||
it('javaJvmStrategy is a function', () => {
|
||||
expect(typeof javaJvmStrategy).toBe('function');
|
||||
it('returns null for imports outside the go module (allows chain to continue)', () => {
|
||||
const ctx = makeCtx(['vendor/other/pkg/foo.go']);
|
||||
ctx.configs.goModule = { modulePath: 'example.com/app' };
|
||||
const result = goPackageStrategy('github.com/other/pkg', 'main.go', ctx);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('kotlinJvmStrategy is a function', () => {
|
||||
expect(typeof kotlinJvmStrategy).toBe('function');
|
||||
it('returns null when goModule is not configured', () => {
|
||||
const ctx = makeCtx(['cmd/server/main.go']);
|
||||
expect(ctx.configs.goModule).toBeNull();
|
||||
const result = goPackageStrategy('example.com/app/cmd/server', 'main.go', ctx);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('rustModuleStrategy is a function', () => {
|
||||
expect(typeof rustModuleStrategy).toBe('function');
|
||||
it('goImportConfig full chain produces the package-kind result (strategy-order guard)', () => {
|
||||
const files = ['cmd/server/main.go', 'cmd/server/handler.go'];
|
||||
const ctx = makeCtx(files);
|
||||
ctx.configs.goModule = { modulePath: 'example.com/app' };
|
||||
|
||||
const resolver = createImportResolver(goImportConfig);
|
||||
const result = resolver('example.com/app/cmd/server', 'main.go', ctx);
|
||||
// If goPackageStrategy were moved after createStandardStrategy, the
|
||||
// standard strategy's suffix resolution would return a single file with
|
||||
// `kind: 'files'` (or null), not `kind: 'package'` with a dirSuffix.
|
||||
expect(result?.kind).toBe('package');
|
||||
});
|
||||
});
|
||||
|
||||
describe('kotlinJvmStrategy', () => {
|
||||
it('resolves wildcard imports to files in the package directory', () => {
|
||||
const files = [
|
||||
'src/main/kotlin/com/example/foo/Bar.kt',
|
||||
'src/main/kotlin/com/example/foo/Baz.kt',
|
||||
'src/main/kotlin/com/example/other/Unrelated.kt',
|
||||
];
|
||||
const ctx = makeCtx(files);
|
||||
|
||||
const result = kotlinJvmStrategy('com.example.foo.*', 'App.kt', ctx);
|
||||
expect(result?.kind).toBe('files');
|
||||
expect(result?.files).toEqual(
|
||||
expect.arrayContaining([
|
||||
'src/main/kotlin/com/example/foo/Bar.kt',
|
||||
'src/main/kotlin/com/example/foo/Baz.kt',
|
||||
]),
|
||||
);
|
||||
expect(result?.files).not.toContain('src/main/kotlin/com/example/other/Unrelated.kt');
|
||||
});
|
||||
|
||||
it('pythonImportStrategy is a function', () => {
|
||||
expect(typeof pythonImportStrategy).toBe('function');
|
||||
it('returns null for wildcard with no matching files (allows chain to continue)', () => {
|
||||
const ctx = makeCtx(['src/main/kotlin/com/example/other/Foo.kt']);
|
||||
const result = kotlinJvmStrategy('com.example.missing.*', 'App.kt', ctx);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('csharpNamespaceStrategy is a function', () => {
|
||||
expect(typeof csharpNamespaceStrategy).toBe('function');
|
||||
it('kotlinImportConfig full chain resolves wildcard via the JVM strategy', () => {
|
||||
const files = ['src/main/kotlin/com/example/foo/Bar.kt'];
|
||||
const ctx = makeCtx(files);
|
||||
const resolver = createImportResolver(kotlinImportConfig);
|
||||
const result = resolver('com.example.foo.*', 'App.kt', ctx);
|
||||
// Standard strategy returns null for `.*` imports (see standard.ts:137),
|
||||
// so only kotlinJvmStrategy can produce this result.
|
||||
expect(result).toEqual({ kind: 'files', files });
|
||||
});
|
||||
});
|
||||
|
||||
describe('csharpNamespaceStrategy', () => {
|
||||
it('resolves namespace imports via .csproj root-namespace mapping', () => {
|
||||
const files = ['src/Services/Auth/AuthService.cs', 'src/Services/Auth/TokenService.cs'];
|
||||
const ctx = makeCtx(files);
|
||||
ctx.configs.csharpConfigs = [{ rootNamespace: 'MyCo', projectDir: 'src' }];
|
||||
|
||||
const result = csharpNamespaceStrategy('MyCo.Services.Auth', 'App.cs', ctx);
|
||||
// Multi-file namespace resolution produces `kind: 'package'` with
|
||||
// dirSuffix — unique to csharpNamespaceStrategy; the standard strategy
|
||||
// always emits `kind: 'files'`.
|
||||
expect(result?.kind).toBe('package');
|
||||
expect(result?.files).toEqual(expect.arrayContaining(files));
|
||||
if (result?.kind === 'package') {
|
||||
expect(result.dirSuffix).toContain('Services/Auth');
|
||||
}
|
||||
});
|
||||
|
||||
it('phpPsr4Strategy is a function', () => {
|
||||
expect(typeof phpPsr4Strategy).toBe('function');
|
||||
it('returns null when no csharpConfigs are configured', () => {
|
||||
const ctx = makeCtx(['src/Services/Auth/AuthService.cs']);
|
||||
expect(ctx.configs.csharpConfigs).toEqual([]);
|
||||
const result = csharpNamespaceStrategy('MyCo.Services.Auth', 'App.cs', ctx);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('swiftPackageStrategy is a function', () => {
|
||||
expect(typeof swiftPackageStrategy).toBe('function');
|
||||
});
|
||||
it('csharpImportConfig full chain produces package-kind (strategy-order guard)', () => {
|
||||
const files = ['src/Services/Auth/AuthService.cs', 'src/Services/Auth/TokenService.cs'];
|
||||
const ctx = makeCtx(files);
|
||||
ctx.configs.csharpConfigs = [{ rootNamespace: 'MyCo', projectDir: 'src' }];
|
||||
|
||||
it('dartPackageStrategy is a function', () => {
|
||||
expect(typeof dartPackageStrategy).toBe('function');
|
||||
});
|
||||
|
||||
it('dartRelativeStrategy is a function', () => {
|
||||
expect(typeof dartRelativeStrategy).toBe('function');
|
||||
});
|
||||
|
||||
it('rubyRequireStrategy is a function', () => {
|
||||
expect(typeof rubyRequireStrategy).toBe('function');
|
||||
const resolver = createImportResolver(csharpImportConfig);
|
||||
const result = resolver('MyCo.Services.Auth', 'App.cs', ctx);
|
||||
// If csharpNamespaceStrategy were reordered after createStandardStrategy,
|
||||
// the result would be `kind: 'files'` (suffix match) or null, never
|
||||
// `kind: 'package'` with a dirSuffix.
|
||||
expect(result?.kind).toBe('package');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue