mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-08 03:08:13 +00:00
refactor(ingestion): split ImportSemantics into per-strategy hooks
- Add ImportResolverStrategy and ImportResolutionConfig types - Create createImportResolver factory (resolver-factory.ts) - Add createStandardStrategy to standard.ts - Extract per-language strategies from existing resolvers: goPackageStrategy, javaJvmStrategy, kotlinJvmStrategy, rustModuleStrategy, pythonImportStrategy, csharpNamespaceStrategy, phpPsr4Strategy, swiftPackageStrategy, dartPackageStrategy, dartRelativeStrategy, rubyRequireStrategy - Create per-language config files in import-resolvers/configs/ - Update all 15 language providers to use createImportResolver(config) - Add 38 unit tests for factory and strategy composition - All 3640+ existing tests pass, tsc --noEmit passes Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/c3aec32d-2155-4808-88df-9cd6b2384174 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
This commit is contained in:
parent
24835341c0
commit
2cc9ecd27f
37 changed files with 780 additions and 78 deletions
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* C / C++ import resolution configs.
|
||||
* Both use standard resolution for #include directives.
|
||||
*/
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { ImportResolutionConfig } from '../types.js';
|
||||
import { createStandardStrategy } from '../standard.js';
|
||||
|
||||
export const cImportConfig: ImportResolutionConfig = {
|
||||
language: SupportedLanguages.C,
|
||||
strategies: [createStandardStrategy(SupportedLanguages.C)],
|
||||
};
|
||||
|
||||
export const cppImportConfig: ImportResolutionConfig = {
|
||||
language: SupportedLanguages.CPlusPlus,
|
||||
strategies: [createStandardStrategy(SupportedLanguages.CPlusPlus)],
|
||||
};
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* C# import resolution config.
|
||||
* Namespace-based strategy via .csproj configs, then standard fallback.
|
||||
*/
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { ImportResolutionConfig } from '../types.js';
|
||||
import { createStandardStrategy } from '../standard.js';
|
||||
import { csharpNamespaceStrategy } from '../csharp.js';
|
||||
|
||||
export const csharpImportConfig: ImportResolutionConfig = {
|
||||
language: SupportedLanguages.CSharp,
|
||||
strategies: [csharpNamespaceStrategy, createStandardStrategy(SupportedLanguages.CSharp)],
|
||||
};
|
||||
13
gitnexus/src/core/ingestion/import-resolvers/configs/dart.ts
Normal file
13
gitnexus/src/core/ingestion/import-resolvers/configs/dart.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* Dart import resolution config.
|
||||
* SDK/package strategy first, then relative import strategy (with ./ prepending).
|
||||
*/
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { ImportResolutionConfig } from '../types.js';
|
||||
import { dartPackageStrategy, dartRelativeStrategy } from '../dart.js';
|
||||
|
||||
export const dartImportConfig: ImportResolutionConfig = {
|
||||
language: SupportedLanguages.Dart,
|
||||
strategies: [dartPackageStrategy, dartRelativeStrategy],
|
||||
};
|
||||
14
gitnexus/src/core/ingestion/import-resolvers/configs/go.ts
Normal file
14
gitnexus/src/core/ingestion/import-resolvers/configs/go.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Go import resolution config.
|
||||
* Go-specific package strategy (go.mod), then standard fallback.
|
||||
*/
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { ImportResolutionConfig } from '../types.js';
|
||||
import { createStandardStrategy } from '../standard.js';
|
||||
import { goPackageStrategy } from '../go.js';
|
||||
|
||||
export const goImportConfig: ImportResolutionConfig = {
|
||||
language: SupportedLanguages.Go,
|
||||
strategies: [goPackageStrategy, createStandardStrategy(SupportedLanguages.Go)],
|
||||
};
|
||||
19
gitnexus/src/core/ingestion/import-resolvers/configs/jvm.ts
Normal file
19
gitnexus/src/core/ingestion/import-resolvers/configs/jvm.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* Java / Kotlin import resolution configs.
|
||||
* JVM-specific wildcard/member strategy, then standard fallback.
|
||||
*/
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { ImportResolutionConfig } from '../types.js';
|
||||
import { createStandardStrategy } from '../standard.js';
|
||||
import { javaJvmStrategy, kotlinJvmStrategy } from '../jvm.js';
|
||||
|
||||
export const javaImportConfig: ImportResolutionConfig = {
|
||||
language: SupportedLanguages.Java,
|
||||
strategies: [javaJvmStrategy, createStandardStrategy(SupportedLanguages.Java)],
|
||||
};
|
||||
|
||||
export const kotlinImportConfig: ImportResolutionConfig = {
|
||||
language: SupportedLanguages.Kotlin,
|
||||
strategies: [kotlinJvmStrategy, createStandardStrategy(SupportedLanguages.Kotlin)],
|
||||
};
|
||||
13
gitnexus/src/core/ingestion/import-resolvers/configs/php.ts
Normal file
13
gitnexus/src/core/ingestion/import-resolvers/configs/php.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* PHP import resolution config.
|
||||
* PSR-4 strategy via composer.json — no standard fallback (PSR-4 includes its own suffix matching).
|
||||
*/
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { ImportResolutionConfig } from '../types.js';
|
||||
import { phpPsr4Strategy } from '../php.js';
|
||||
|
||||
export const phpImportConfig: ImportResolutionConfig = {
|
||||
language: SupportedLanguages.PHP,
|
||||
strategies: [phpPsr4Strategy],
|
||||
};
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Python import resolution config.
|
||||
* PEP 328 relative + proximity-based strategy, then standard fallback.
|
||||
*/
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { ImportResolutionConfig } from '../types.js';
|
||||
import { createStandardStrategy } from '../standard.js';
|
||||
import { pythonImportStrategy } from '../python.js';
|
||||
|
||||
export const pythonImportConfig: ImportResolutionConfig = {
|
||||
language: SupportedLanguages.Python,
|
||||
strategies: [pythonImportStrategy, createStandardStrategy(SupportedLanguages.Python)],
|
||||
};
|
||||
13
gitnexus/src/core/ingestion/import-resolvers/configs/ruby.ts
Normal file
13
gitnexus/src/core/ingestion/import-resolvers/configs/ruby.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* Ruby import resolution config.
|
||||
* Require/require_relative suffix matching — no standard fallback.
|
||||
*/
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { ImportResolutionConfig } from '../types.js';
|
||||
import { rubyRequireStrategy } from '../ruby.js';
|
||||
|
||||
export const rubyImportConfig: ImportResolutionConfig = {
|
||||
language: SupportedLanguages.Ruby,
|
||||
strategies: [rubyRequireStrategy],
|
||||
};
|
||||
14
gitnexus/src/core/ingestion/import-resolvers/configs/rust.ts
Normal file
14
gitnexus/src/core/ingestion/import-resolvers/configs/rust.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Rust import resolution config.
|
||||
* Rust module strategy (grouped imports, crate/super/self paths), then standard fallback.
|
||||
*/
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { ImportResolutionConfig } from '../types.js';
|
||||
import { createStandardStrategy } from '../standard.js';
|
||||
import { rustModuleStrategy } from '../rust.js';
|
||||
|
||||
export const rustImportConfig: ImportResolutionConfig = {
|
||||
language: SupportedLanguages.Rust,
|
||||
strategies: [rustModuleStrategy, createStandardStrategy(SupportedLanguages.Rust)],
|
||||
};
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* Swift import resolution config.
|
||||
* Package.swift target map strategy — no standard fallback (unresolved = external framework).
|
||||
*/
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { ImportResolutionConfig } from '../types.js';
|
||||
import { swiftPackageStrategy } from '../swift.js';
|
||||
|
||||
export const swiftImportConfig: ImportResolutionConfig = {
|
||||
language: SupportedLanguages.Swift,
|
||||
strategies: [swiftPackageStrategy],
|
||||
};
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* TypeScript / JavaScript / Vue import resolution configs.
|
||||
* All use standard resolution — TS/JS with tsconfig path aliases,
|
||||
* Vue delegates to TypeScript's resolver.
|
||||
*/
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { ImportResolutionConfig } from '../types.js';
|
||||
import { createStandardStrategy } from '../standard.js';
|
||||
|
||||
export const typescriptImportConfig: ImportResolutionConfig = {
|
||||
language: SupportedLanguages.TypeScript,
|
||||
strategies: [createStandardStrategy(SupportedLanguages.TypeScript)],
|
||||
};
|
||||
|
||||
export const javascriptImportConfig: ImportResolutionConfig = {
|
||||
language: SupportedLanguages.JavaScript,
|
||||
strategies: [createStandardStrategy(SupportedLanguages.JavaScript)],
|
||||
};
|
||||
|
||||
export const vueImportConfig: ImportResolutionConfig = {
|
||||
language: SupportedLanguages.Vue,
|
||||
strategies: [createStandardStrategy(SupportedLanguages.TypeScript)],
|
||||
};
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
import type { SuffixIndex } from './utils.js';
|
||||
import { suffixResolve } from './utils.js';
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { ImportResult, ResolveCtx } from './types.js';
|
||||
import type { ImportResult, ImportResolverStrategy, ResolveCtx } from './types.js';
|
||||
import { resolveStandard } from './standard.js';
|
||||
import type { CSharpProjectConfig } from '../language-config.js';
|
||||
|
||||
|
|
@ -127,12 +127,12 @@ export function resolveCSharpNamespaceDir(
|
|||
return null;
|
||||
}
|
||||
|
||||
/** C#: namespace-based resolution via .csproj configs, with suffix-match fallback. */
|
||||
export function resolveCSharpImport(
|
||||
rawImportPath: string,
|
||||
filePath: string,
|
||||
ctx: ResolveCtx,
|
||||
): ImportResult {
|
||||
/** C# namespace-based resolution strategy via .csproj configs. */
|
||||
export const csharpNamespaceStrategy: ImportResolverStrategy = (
|
||||
rawImportPath,
|
||||
_filePath,
|
||||
ctx,
|
||||
) => {
|
||||
const csharpConfigs = ctx.configs.csharpConfigs;
|
||||
if (csharpConfigs.length > 0) {
|
||||
const resolvedFiles = resolveCSharpImportInternal(
|
||||
|
|
@ -150,5 +150,15 @@ export function resolveCSharpImport(
|
|||
}
|
||||
if (resolvedFiles.length > 0) return { kind: 'files', files: resolvedFiles };
|
||||
}
|
||||
return resolveStandard(rawImportPath, filePath, ctx, SupportedLanguages.CSharp);
|
||||
return null;
|
||||
};
|
||||
|
||||
/** C#: namespace-based resolution via .csproj configs, with suffix-match fallback. */
|
||||
export function resolveCSharpImport(
|
||||
rawImportPath: string,
|
||||
filePath: string,
|
||||
ctx: ResolveCtx,
|
||||
): ImportResult {
|
||||
return csharpNamespaceStrategy(rawImportPath, filePath, ctx)
|
||||
?? resolveStandard(rawImportPath, filePath, ctx, SupportedLanguages.CSharp);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,63 @@
|
|||
* SDK imports (dart:*) and external packages are skipped.
|
||||
*/
|
||||
|
||||
import type { ImportResult, ResolveCtx } from './types.js';
|
||||
import type { ImportResult, ImportResolverStrategy, ResolveCtx } from './types.js';
|
||||
import { resolveStandard } from './standard.js';
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
|
||||
/**
|
||||
* Dart SDK and package: import strategy.
|
||||
* 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,
|
||||
) => {
|
||||
// Strip surrounding quotes from configurable_uri capture
|
||||
const stripped = rawImportPath.replace(/^['"]|['"]$/g, '');
|
||||
|
||||
// Skip dart: SDK imports (dart:async, dart:io, etc.)
|
||||
if (stripped.startsWith('dart:')) return { kind: 'files', files: [] };
|
||||
|
||||
// Local package: imports → resolve to lib/<path>
|
||||
if (stripped.startsWith('package:')) {
|
||||
const slashIdx = stripped.indexOf('/');
|
||||
if (slashIdx === -1) return { kind: 'files', files: [] };
|
||||
const relPath = stripped.slice(slashIdx + 1);
|
||||
const candidates = [`lib/${relPath}`, relPath];
|
||||
const files: string[] = [];
|
||||
for (const candidate of candidates) {
|
||||
for (const fp of ctx.allFileList) {
|
||||
if (fp.endsWith('/' + candidate) || fp === candidate) {
|
||||
files.push(fp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (files.length > 0) break;
|
||||
}
|
||||
if (files.length > 0) return { kind: 'files', files };
|
||||
return { kind: 'files', files: [] }; // external package
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Dart relative import strategy — prepends "./" for bare relative paths,
|
||||
* then delegates to standard resolution.
|
||||
*/
|
||||
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);
|
||||
};
|
||||
|
||||
export function resolveDartImport(
|
||||
rawImportPath: string,
|
||||
filePath: string,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { ImportResult, ResolveCtx } from './types.js';
|
||||
import type { ImportResult, ImportResolverStrategy, ResolveCtx } from './types.js';
|
||||
import { resolveStandard } from './standard.js';
|
||||
import type { GoModuleConfig } from '../language-config.js';
|
||||
|
||||
|
|
@ -57,12 +57,12 @@ export function resolveGoPackage(
|
|||
return matches;
|
||||
}
|
||||
|
||||
/** Go: package-level imports via go.mod module path. */
|
||||
export function resolveGoImport(
|
||||
rawImportPath: string,
|
||||
filePath: string,
|
||||
ctx: ResolveCtx,
|
||||
): ImportResult {
|
||||
/** Go-specific package resolution strategy — resolves go.mod-based package imports. */
|
||||
export const goPackageStrategy: ImportResolverStrategy = (
|
||||
rawImportPath,
|
||||
_filePath,
|
||||
ctx,
|
||||
) => {
|
||||
const goModule = ctx.configs.goModule;
|
||||
if (goModule && rawImportPath.startsWith(goModule.modulePath)) {
|
||||
const pkgSuffix = resolveGoPackageDir(rawImportPath, goModule);
|
||||
|
|
@ -79,5 +79,15 @@ export function resolveGoImport(
|
|||
}
|
||||
// Fall through if no files found (package might be external)
|
||||
}
|
||||
return resolveStandard(rawImportPath, filePath, ctx, SupportedLanguages.Go);
|
||||
return null;
|
||||
};
|
||||
|
||||
/** Go: package-level imports via go.mod module path. */
|
||||
export function resolveGoImport(
|
||||
rawImportPath: string,
|
||||
filePath: string,
|
||||
ctx: ResolveCtx,
|
||||
): ImportResult {
|
||||
return goPackageStrategy(rawImportPath, filePath, ctx)
|
||||
?? resolveStandard(rawImportPath, filePath, ctx, SupportedLanguages.Go);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import type { SuffixIndex } from './utils.js';
|
||||
import type { SyntaxNode } from '../utils/ast-helpers.js';
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { ImportResult, ResolveCtx } from './types.js';
|
||||
import type { ImportResult, ImportResolverStrategy, ResolveCtx } from './types.js';
|
||||
import { resolveStandard } from './standard.js';
|
||||
|
||||
/** Kotlin file extensions for JVM resolver reuse */
|
||||
|
|
@ -126,12 +126,12 @@ export function resolveJvmMemberImport(
|
|||
return null;
|
||||
}
|
||||
|
||||
/** Java: JVM wildcard -> member import -> standard fallthrough */
|
||||
export function resolveJavaImport(
|
||||
rawImportPath: string,
|
||||
filePath: string,
|
||||
ctx: ResolveCtx,
|
||||
): ImportResult {
|
||||
/** Java JVM resolution strategy — wildcard and member import resolution. */
|
||||
export const javaJvmStrategy: ImportResolverStrategy = (
|
||||
rawImportPath,
|
||||
_filePath,
|
||||
ctx,
|
||||
) => {
|
||||
if (rawImportPath.endsWith('.*')) {
|
||||
const matchedFiles = resolveJvmWildcard(
|
||||
rawImportPath,
|
||||
|
|
@ -151,18 +151,27 @@ export function resolveJavaImport(
|
|||
);
|
||||
if (memberResolved) return { kind: 'files', files: [memberResolved] };
|
||||
}
|
||||
return resolveStandard(rawImportPath, filePath, ctx, SupportedLanguages.Java);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Kotlin: JVM wildcard/member with Java-interop fallback -> top-level function imports -> standard.
|
||||
* Kotlin can import from .kt/.kts files OR from .java files (Java interop).
|
||||
*/
|
||||
export function resolveKotlinImport(
|
||||
/** Java: JVM wildcard -> member import -> standard fallthrough */
|
||||
export function resolveJavaImport(
|
||||
rawImportPath: string,
|
||||
filePath: string,
|
||||
ctx: ResolveCtx,
|
||||
): ImportResult {
|
||||
return javaJvmStrategy(rawImportPath, filePath, ctx)
|
||||
?? resolveStandard(rawImportPath, filePath, ctx, SupportedLanguages.Java);
|
||||
}
|
||||
|
||||
/**
|
||||
* Kotlin JVM resolution strategy — wildcard/member with Java-interop + top-level function imports.
|
||||
*/
|
||||
export const kotlinJvmStrategy: ImportResolverStrategy = (
|
||||
rawImportPath,
|
||||
_filePath,
|
||||
ctx,
|
||||
) => {
|
||||
if (rawImportPath.endsWith('.*')) {
|
||||
const matchedFiles = resolveJvmWildcard(
|
||||
rawImportPath,
|
||||
|
|
@ -228,5 +237,18 @@ export function resolveKotlinImport(
|
|||
if (dirFiles.length > 0) return { kind: 'files', files: dirFiles };
|
||||
}
|
||||
}
|
||||
return resolveStandard(rawImportPath, filePath, ctx, SupportedLanguages.Kotlin);
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Kotlin: JVM wildcard/member with Java-interop fallback -> top-level function imports -> standard.
|
||||
* Kotlin can import from .kt/.kts files OR from .java files (Java interop).
|
||||
*/
|
||||
export function resolveKotlinImport(
|
||||
rawImportPath: string,
|
||||
filePath: string,
|
||||
ctx: ResolveCtx,
|
||||
): ImportResult {
|
||||
return kotlinJvmStrategy(rawImportPath, filePath, ctx)
|
||||
?? resolveStandard(rawImportPath, filePath, ctx, SupportedLanguages.Kotlin);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import type { SuffixIndex } from './utils.js';
|
||||
import { suffixResolve } from './utils.js';
|
||||
import type { ImportResult, ResolveCtx } from './types.js';
|
||||
import type { ImportResult, ImportResolverStrategy, ResolveCtx } from './types.js';
|
||||
import type { ComposerConfig } from '../language-config.js';
|
||||
|
||||
/** Get or compute the sorted PSR-4 entries (cached after first call). */
|
||||
|
|
@ -92,12 +92,12 @@ export function resolvePhpImportInternal(
|
|||
return suffixResolve(pathParts, normalizedFileList, allFileList, index);
|
||||
}
|
||||
|
||||
/** PHP: namespace-based resolution via composer.json PSR-4. */
|
||||
export function resolvePhpImport(
|
||||
rawImportPath: string,
|
||||
_filePath: string,
|
||||
ctx: ResolveCtx,
|
||||
): ImportResult {
|
||||
/** PHP PSR-4 resolution strategy via composer.json autoload mappings. */
|
||||
export const phpPsr4Strategy: ImportResolverStrategy = (
|
||||
rawImportPath,
|
||||
_filePath,
|
||||
ctx,
|
||||
) => {
|
||||
const resolved = resolvePhpImportInternal(
|
||||
rawImportPath,
|
||||
ctx.configs.composerConfig,
|
||||
|
|
@ -107,4 +107,13 @@ export function resolvePhpImport(
|
|||
ctx.index,
|
||||
);
|
||||
return resolved ? { kind: 'files', files: [resolved] } : null;
|
||||
};
|
||||
|
||||
/** PHP: namespace-based resolution via composer.json PSR-4. */
|
||||
export function resolvePhpImport(
|
||||
rawImportPath: string,
|
||||
_filePath: string,
|
||||
ctx: ResolveCtx,
|
||||
): ImportResult {
|
||||
return phpPsr4Strategy(rawImportPath, _filePath, ctx);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { tryResolveWithExtensions } from './utils.js';
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { ImportResult, ResolveCtx } from './types.js';
|
||||
import type { ImportResult, ImportResolverStrategy, ResolveCtx } from './types.js';
|
||||
import { resolveStandard } from './standard.js';
|
||||
|
||||
/**
|
||||
|
|
@ -75,6 +75,26 @@ export function resolvePythonImportInternal(
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Python import resolution strategy — PEP 328 relative + proximity-based bare imports.
|
||||
* 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,
|
||||
) => {
|
||||
const resolved = resolvePythonImportInternal(filePath, rawImportPath, ctx.allFilePaths);
|
||||
if (resolved) {
|
||||
ctx.resolveCache.set(`${filePath}::${rawImportPath}`, resolved);
|
||||
return { kind: 'files', files: [resolved] };
|
||||
}
|
||||
// PEP 328: unresolved relative imports should not fall through to suffix matching
|
||||
if (rawImportPath.startsWith('.')) return { kind: 'files', files: [] };
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Python: relative imports (PEP 328) + proximity-based bare imports.
|
||||
* Falls through to standard suffix resolution when proximity finds no match.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* Import resolver factory — creates a composable import resolver from
|
||||
* an ordered list of strategies.
|
||||
*
|
||||
* Mirrors the method-extractors/generic.ts and call-extractors/generic.ts
|
||||
* pattern: declare a config per language, produce a runtime resolver via factory.
|
||||
*
|
||||
* Each strategy is tried in order. The first non-null result wins.
|
||||
* A result with an empty `files` array is treated as "handled but unresolved"
|
||||
* (stops the chain without producing import edges).
|
||||
*/
|
||||
|
||||
import type { ImportResolverFn, ImportResolutionConfig } from './types.js';
|
||||
|
||||
/**
|
||||
* Create an ImportResolverFn from a declarative config.
|
||||
*
|
||||
* Chains strategies in declaration order — first non-null result wins.
|
||||
* Returns null only if every strategy returns null.
|
||||
*/
|
||||
export function createImportResolver(config: ImportResolutionConfig): ImportResolverFn {
|
||||
const { strategies } = config;
|
||||
return (rawImportPath, filePath, ctx) => {
|
||||
for (const strategy of strategies) {
|
||||
const result = strategy(rawImportPath, filePath, ctx);
|
||||
if (result) return result;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import type { SuffixIndex } from './utils.js';
|
||||
import { suffixResolve } from './utils.js';
|
||||
import type { ImportResult, ResolveCtx } from './types.js';
|
||||
import type { ImportResult, ImportResolverStrategy, ResolveCtx } from './types.js';
|
||||
|
||||
/**
|
||||
* Resolve a Ruby require/require_relative path to a matching .rb file (low-level helper).
|
||||
|
|
@ -23,12 +23,12 @@ export function resolveRubyImportInternal(
|
|||
return suffixResolve(pathParts, normalizedFileList, allFileList, index);
|
||||
}
|
||||
|
||||
/** Ruby: require / require_relative. */
|
||||
export function resolveRubyImport(
|
||||
rawImportPath: string,
|
||||
_filePath: string,
|
||||
ctx: ResolveCtx,
|
||||
): ImportResult {
|
||||
/** Ruby require/require_relative resolution strategy. */
|
||||
export const rubyRequireStrategy: ImportResolverStrategy = (
|
||||
rawImportPath,
|
||||
_filePath,
|
||||
ctx,
|
||||
) => {
|
||||
const resolved = resolveRubyImportInternal(
|
||||
rawImportPath,
|
||||
ctx.normalizedFileList,
|
||||
|
|
@ -36,4 +36,13 @@ export function resolveRubyImport(
|
|||
ctx.index,
|
||||
);
|
||||
return resolved ? { kind: 'files', files: [resolved] } : null;
|
||||
};
|
||||
|
||||
/** Ruby: require / require_relative. */
|
||||
export function resolveRubyImport(
|
||||
rawImportPath: string,
|
||||
_filePath: string,
|
||||
ctx: ResolveCtx,
|
||||
): ImportResult {
|
||||
return rubyRequireStrategy(rawImportPath, _filePath, ctx);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { ImportResult, ResolveCtx } from './types.js';
|
||||
import type { ImportResult, ImportResolverStrategy, ResolveCtx } from './types.js';
|
||||
import { resolveStandard } from './standard.js';
|
||||
|
||||
/**
|
||||
|
|
@ -85,12 +85,12 @@ export function tryRustModulePath(modulePath: string, allFiles: Set<string>): st
|
|||
return null;
|
||||
}
|
||||
|
||||
/** Rust: expand grouped imports: use {crate::a, crate::b} and use crate::models::{User, Repo}. */
|
||||
export function resolveRustImport(
|
||||
rawImportPath: string,
|
||||
filePath: string,
|
||||
ctx: ResolveCtx,
|
||||
): ImportResult {
|
||||
/** Rust module resolution strategy — handles grouped imports and crate/super/self paths. */
|
||||
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);
|
||||
|
|
@ -128,5 +128,15 @@ export function resolveRustImport(
|
|||
if (prefixResult) return { kind: 'files', files: [prefixResult] };
|
||||
}
|
||||
|
||||
return resolveStandard(rawImportPath, filePath, ctx, SupportedLanguages.Rust);
|
||||
return null;
|
||||
};
|
||||
|
||||
/** Rust: expand grouped imports: use {crate::a, crate::b} and use crate::models::{User, Repo}. */
|
||||
export function resolveRustImport(
|
||||
rawImportPath: string,
|
||||
filePath: string,
|
||||
ctx: ResolveCtx,
|
||||
): ImportResult {
|
||||
return rustModuleStrategy(rawImportPath, filePath, ctx)
|
||||
?? resolveStandard(rawImportPath, filePath, ctx, SupportedLanguages.Rust);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import type { SuffixIndex } from './utils.js';
|
|||
import { tryResolveWithExtensions, suffixResolve } from './utils.js';
|
||||
import { resolveRustImportInternal } from './rust.js';
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { ImportResult, ImportResolverFn, ResolveCtx } from './types.js';
|
||||
import type { ImportResult, ImportResolverFn, ImportResolverStrategy, ResolveCtx } from './types.js';
|
||||
import type { TsconfigPaths } from '../language-config.js';
|
||||
|
||||
/** Max entries in the resolve cache. Beyond this, entries are evicted.
|
||||
|
|
@ -189,3 +189,12 @@ export const resolveCImport: ImportResolverFn = (raw, fp, ctx) =>
|
|||
/** C++: standard single-file resolution for #include directives. */
|
||||
export const resolveCppImport: ImportResolverFn = (raw, fp, ctx) =>
|
||||
resolveStandard(raw, fp, ctx, SupportedLanguages.CPlusPlus);
|
||||
|
||||
// ============================================================================
|
||||
// Strategy factory — composable hook for ImportResolutionConfig
|
||||
// ============================================================================
|
||||
|
||||
/** Create a reusable standard-resolution strategy for a given language. */
|
||||
export function createStandardStrategy(language: SupportedLanguages): ImportResolverStrategy {
|
||||
return (raw, fp, ctx) => resolveStandard(raw, fp, ctx, language);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@
|
|||
* Handles module imports via Package.swift target map.
|
||||
*/
|
||||
|
||||
import type { ImportResult, ResolveCtx } from './types.js';
|
||||
import type { ImportResult, ImportResolverStrategy, ResolveCtx } from './types.js';
|
||||
|
||||
/** Swift: module imports via Package.swift target map. */
|
||||
export function resolveSwiftImport(
|
||||
rawImportPath: string,
|
||||
_filePath: string,
|
||||
ctx: ResolveCtx,
|
||||
): ImportResult {
|
||||
/** Swift Package.swift target map resolution strategy. */
|
||||
export const swiftPackageStrategy: ImportResolverStrategy = (
|
||||
rawImportPath,
|
||||
_filePath,
|
||||
ctx,
|
||||
) => {
|
||||
const swiftPackageConfig = ctx.configs.swiftPackageConfig;
|
||||
if (swiftPackageConfig) {
|
||||
const targetDir = swiftPackageConfig.targets.get(rawImportPath);
|
||||
|
|
@ -29,4 +29,13 @@ export function resolveSwiftImport(
|
|||
}
|
||||
}
|
||||
return null; // External framework (Foundation, UIKit, etc.)
|
||||
};
|
||||
|
||||
/** Swift: module imports via Package.swift target map. */
|
||||
export function resolveSwiftImport(
|
||||
rawImportPath: string,
|
||||
_filePath: string,
|
||||
ctx: ResolveCtx,
|
||||
): ImportResult {
|
||||
return swiftPackageStrategy(rawImportPath, _filePath, ctx);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import type {
|
|||
} from '../language-config.js';
|
||||
import type { SwiftPackageConfig } from '../language-config.js';
|
||||
import type { SuffixIndex } from './utils.js';
|
||||
import type { SupportedLanguages } from 'gitnexus-shared';
|
||||
|
||||
/**
|
||||
* Result of resolving an import via language-specific dispatch.
|
||||
|
|
@ -53,3 +54,22 @@ export type ImportResolverFn = (
|
|||
filePath: string,
|
||||
resolveCtx: ResolveCtx,
|
||||
) => ImportResult;
|
||||
|
||||
/**
|
||||
* A single import resolution strategy — one step in a composable chain.
|
||||
* Same signature as ImportResolverFn. Returns null to let the next strategy
|
||||
* in the chain try; returns a result (even with empty files) to stop the chain.
|
||||
*/
|
||||
export type ImportResolverStrategy = ImportResolverFn;
|
||||
|
||||
/**
|
||||
* Declarative config for composable import resolution — mirrors the
|
||||
* MethodExtractionConfig / CallExtractionConfig pattern.
|
||||
*
|
||||
* Each language declares an ordered list of strategies to try.
|
||||
* The factory (`createImportResolver`) chains them: first non-null result wins.
|
||||
*/
|
||||
export interface ImportResolutionConfig {
|
||||
readonly language: SupportedLanguages;
|
||||
readonly strategies: readonly ImportResolverStrategy[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import { defineLanguage } from '../language-provider.js';
|
|||
import { typeConfig as cCppConfig } from '../type-extractors/c-cpp.js';
|
||||
import { cCppExportChecker } from '../export-detection.js';
|
||||
import { resolveCImport, resolveCppImport } from '../import-resolvers/standard.js';
|
||||
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
|
||||
import { cImportConfig, cppImportConfig } from '../import-resolvers/configs/c-cpp.js';
|
||||
import { C_QUERIES, CPP_QUERIES } from '../tree-sitter-queries.js';
|
||||
|
||||
/**
|
||||
|
|
@ -318,7 +320,7 @@ export const cProvider = defineLanguage({
|
|||
treeSitterQueries: C_QUERIES,
|
||||
typeConfig: cCppConfig,
|
||||
exportChecker: cCppExportChecker,
|
||||
importResolver: resolveCImport,
|
||||
importResolver: createImportResolver(cImportConfig),
|
||||
importSemantics: 'wildcard-transitive',
|
||||
callExtractor: createCallExtractor(cCallConfig),
|
||||
fieldExtractor: createFieldExtractor(cFieldConfig),
|
||||
|
|
@ -338,7 +340,7 @@ export const cppProvider = defineLanguage({
|
|||
treeSitterQueries: CPP_QUERIES,
|
||||
typeConfig: cCppConfig,
|
||||
exportChecker: cCppExportChecker,
|
||||
importResolver: resolveCppImport,
|
||||
importResolver: createImportResolver(cppImportConfig),
|
||||
importSemantics: 'wildcard-transitive',
|
||||
mroStrategy: 'leftmost-base',
|
||||
callExtractor: createCallExtractor(cppCallConfig),
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import { defineLanguage } from '../language-provider.js';
|
|||
import { typeConfig as csharpConfig } from '../type-extractors/csharp.js';
|
||||
import { csharpExportChecker } from '../export-detection.js';
|
||||
import { resolveCSharpImport } from '../import-resolvers/csharp.js';
|
||||
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
|
||||
import { csharpImportConfig } from '../import-resolvers/configs/csharp.js';
|
||||
import { extractCSharpNamedBindings } from '../named-bindings/csharp.js';
|
||||
import { CSHARP_QUERIES } from '../tree-sitter-queries.js';
|
||||
import { createCallExtractor } from '../call-extractors/generic.js';
|
||||
|
|
@ -125,7 +127,7 @@ export const csharpProvider = defineLanguage({
|
|||
treeSitterQueries: CSHARP_QUERIES,
|
||||
typeConfig: csharpConfig,
|
||||
exportChecker: csharpExportChecker,
|
||||
importResolver: resolveCSharpImport,
|
||||
importResolver: createImportResolver(csharpImportConfig),
|
||||
namedBindingExtractor: extractCSharpNamedBindings,
|
||||
interfaceNamePattern: /^I[A-Z]/,
|
||||
mroStrategy: 'implements-split',
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ import { defineLanguage } from '../language-provider.js';
|
|||
import { typeConfig as dartConfig } from '../type-extractors/dart.js';
|
||||
import { dartExportChecker } from '../export-detection.js';
|
||||
import { resolveDartImport } from '../import-resolvers/dart.js';
|
||||
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
|
||||
import { dartImportConfig } from '../import-resolvers/configs/dart.js';
|
||||
import { DART_QUERIES } from '../tree-sitter-queries.js';
|
||||
import { createFieldExtractor } from '../field-extractors/generic.js';
|
||||
import { dartConfig as dartFieldConfig } from '../field-extractors/configs/dart.js';
|
||||
|
|
@ -94,7 +96,7 @@ export const dartProvider = defineLanguage({
|
|||
treeSitterQueries: DART_QUERIES,
|
||||
typeConfig: dartConfig,
|
||||
exportChecker: dartExportChecker,
|
||||
importResolver: resolveDartImport,
|
||||
importResolver: createImportResolver(dartImportConfig),
|
||||
importSemantics: 'wildcard-leaf',
|
||||
callExtractor: createCallExtractor(dartCallConfig),
|
||||
fieldExtractor: createFieldExtractor(dartFieldConfig),
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ import { defineLanguage } from '../language-provider.js';
|
|||
import { typeConfig as goConfig } from '../type-extractors/go.js';
|
||||
import { goExportChecker } from '../export-detection.js';
|
||||
import { resolveGoImport } from '../import-resolvers/go.js';
|
||||
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
|
||||
import { goImportConfig } from '../import-resolvers/configs/go.js';
|
||||
import { GO_QUERIES } from '../tree-sitter-queries.js';
|
||||
import { createFieldExtractor } from '../field-extractors/generic.js';
|
||||
import { goConfig as goFieldConfig } from '../field-extractors/configs/go.js';
|
||||
|
|
@ -32,7 +34,7 @@ export const goProvider = defineLanguage({
|
|||
treeSitterQueries: GO_QUERIES,
|
||||
typeConfig: goConfig,
|
||||
exportChecker: goExportChecker,
|
||||
importResolver: resolveGoImport,
|
||||
importResolver: createImportResolver(goImportConfig),
|
||||
importSemantics: 'wildcard-leaf',
|
||||
callExtractor: createCallExtractor(goCallConfig),
|
||||
fieldExtractor: createFieldExtractor(goFieldConfig),
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import { defineLanguage } from '../language-provider.js';
|
|||
import { javaTypeConfig } from '../type-extractors/jvm.js';
|
||||
import { javaExportChecker } from '../export-detection.js';
|
||||
import { resolveJavaImport } from '../import-resolvers/jvm.js';
|
||||
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
|
||||
import { javaImportConfig } from '../import-resolvers/configs/jvm.js';
|
||||
import { extractJavaNamedBindings } from '../named-bindings/java.js';
|
||||
import { JAVA_QUERIES } from '../tree-sitter-queries.js';
|
||||
import { createCallExtractor } from '../call-extractors/generic.js';
|
||||
|
|
@ -31,7 +33,7 @@ export const javaProvider = defineLanguage({
|
|||
treeSitterQueries: JAVA_QUERIES,
|
||||
typeConfig: javaTypeConfig,
|
||||
exportChecker: javaExportChecker,
|
||||
importResolver: resolveJavaImport,
|
||||
importResolver: createImportResolver(javaImportConfig),
|
||||
namedBindingExtractor: extractJavaNamedBindings,
|
||||
interfaceNamePattern: /^I[A-Z]/,
|
||||
mroStrategy: 'implements-split',
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import { defineLanguage } from '../language-provider.js';
|
|||
import { kotlinTypeConfig } from '../type-extractors/jvm.js';
|
||||
import { kotlinExportChecker } from '../export-detection.js';
|
||||
import { resolveKotlinImport } from '../import-resolvers/jvm.js';
|
||||
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
|
||||
import { kotlinImportConfig } from '../import-resolvers/configs/jvm.js';
|
||||
import { extractKotlinNamedBindings } from '../named-bindings/kotlin.js';
|
||||
import { appendKotlinWildcard } from '../import-resolvers/jvm.js';
|
||||
import { KOTLIN_QUERIES } from '../tree-sitter-queries.js';
|
||||
|
|
@ -106,7 +108,7 @@ export const kotlinProvider = defineLanguage({
|
|||
treeSitterQueries: KOTLIN_QUERIES,
|
||||
typeConfig: kotlinTypeConfig,
|
||||
exportChecker: kotlinExportChecker,
|
||||
importResolver: resolveKotlinImport,
|
||||
importResolver: createImportResolver(kotlinImportConfig),
|
||||
namedBindingExtractor: extractKotlinNamedBindings,
|
||||
importPathPreprocessor: appendKotlinWildcard,
|
||||
mroStrategy: 'implements-split',
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import { defineLanguage } from '../language-provider.js';
|
|||
import { typeConfig as phpConfig } from '../type-extractors/php.js';
|
||||
import { phpExportChecker } from '../export-detection.js';
|
||||
import { resolvePhpImport } from '../import-resolvers/php.js';
|
||||
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
|
||||
import { phpImportConfig } from '../import-resolvers/configs/php.js';
|
||||
import { extractPhpNamedBindings } from '../named-bindings/php.js';
|
||||
import { PHP_QUERIES } from '../tree-sitter-queries.js';
|
||||
import { findDescendant, extractStringContent, type SyntaxNode } from '../utils/ast-helpers.js';
|
||||
|
|
@ -240,7 +242,7 @@ export const phpProvider = defineLanguage({
|
|||
treeSitterQueries: PHP_QUERIES,
|
||||
typeConfig: phpConfig,
|
||||
exportChecker: phpExportChecker,
|
||||
importResolver: resolvePhpImport,
|
||||
importResolver: createImportResolver(phpImportConfig),
|
||||
namedBindingExtractor: extractPhpNamedBindings,
|
||||
callExtractor: createCallExtractor(phpCallConfig),
|
||||
fieldExtractor: createFieldExtractor(phpFieldConfig),
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import { defineLanguage } from '../language-provider.js';
|
|||
import { typeConfig as pythonConfig } from '../type-extractors/python.js';
|
||||
import { pythonExportChecker } from '../export-detection.js';
|
||||
import { resolvePythonImport } from '../import-resolvers/python.js';
|
||||
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
|
||||
import { pythonImportConfig } from '../import-resolvers/configs/python.js';
|
||||
import { extractPythonNamedBindings } from '../named-bindings/python.js';
|
||||
import { PYTHON_QUERIES } from '../tree-sitter-queries.js';
|
||||
import { createFieldExtractor } from '../field-extractors/generic.js';
|
||||
|
|
@ -64,7 +66,7 @@ export const pythonProvider = defineLanguage({
|
|||
treeSitterQueries: PYTHON_QUERIES,
|
||||
typeConfig: pythonConfig,
|
||||
exportChecker: pythonExportChecker,
|
||||
importResolver: resolvePythonImport,
|
||||
importResolver: createImportResolver(pythonImportConfig),
|
||||
namedBindingExtractor: extractPythonNamedBindings,
|
||||
importSemantics: 'namespace',
|
||||
mroStrategy: 'c3',
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import { typeConfig as rubyConfig } from '../type-extractors/ruby.js';
|
|||
import { routeRubyCall } from '../call-routing.js';
|
||||
import { rubyExportChecker } from '../export-detection.js';
|
||||
import { resolveRubyImport } from '../import-resolvers/ruby.js';
|
||||
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
|
||||
import { rubyImportConfig } from '../import-resolvers/configs/ruby.js';
|
||||
import { RUBY_QUERIES } from '../tree-sitter-queries.js';
|
||||
import { createFieldExtractor } from '../field-extractors/generic.js';
|
||||
import { rubyConfig as rubyFieldConfig } from '../field-extractors/configs/ruby.js';
|
||||
|
|
@ -110,7 +112,7 @@ export const rubyProvider = defineLanguage({
|
|||
treeSitterQueries: RUBY_QUERIES,
|
||||
typeConfig: rubyConfig,
|
||||
exportChecker: rubyExportChecker,
|
||||
importResolver: resolveRubyImport,
|
||||
importResolver: createImportResolver(rubyImportConfig),
|
||||
callRouter: routeRubyCall,
|
||||
importSemantics: 'wildcard-leaf',
|
||||
callExtractor: createCallExtractor(rubyCallConfig),
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import type { SyntaxNode } from '../utils/ast-helpers.js';
|
|||
import { typeConfig as rustConfig } from '../type-extractors/rust.js';
|
||||
import { rustExportChecker } from '../export-detection.js';
|
||||
import { resolveRustImport } from '../import-resolvers/rust.js';
|
||||
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
|
||||
import { rustImportConfig } from '../import-resolvers/configs/rust.js';
|
||||
import { extractRustNamedBindings } from '../named-bindings/rust.js';
|
||||
import { RUST_QUERIES } from '../tree-sitter-queries.js';
|
||||
import { createFieldExtractor } from '../field-extractors/generic.js';
|
||||
|
|
@ -122,7 +124,7 @@ export const rustProvider = defineLanguage({
|
|||
treeSitterQueries: RUST_QUERIES,
|
||||
typeConfig: rustConfig,
|
||||
exportChecker: rustExportChecker,
|
||||
importResolver: resolveRustImport,
|
||||
importResolver: createImportResolver(rustImportConfig),
|
||||
namedBindingExtractor: extractRustNamedBindings,
|
||||
mroStrategy: 'qualified-syntax',
|
||||
callExtractor: createCallExtractor(rustCallConfig),
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import { defineLanguage } from '../language-provider.js';
|
|||
import { typeConfig as swiftConfig } from '../type-extractors/swift.js';
|
||||
import { swiftExportChecker } from '../export-detection.js';
|
||||
import { resolveSwiftImport } from '../import-resolvers/swift.js';
|
||||
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
|
||||
import { swiftImportConfig } from '../import-resolvers/configs/swift.js';
|
||||
import { SWIFT_QUERIES } from '../tree-sitter-queries.js';
|
||||
import type { SwiftPackageConfig } from '../language-config.js';
|
||||
import type { SyntaxNode } from '../utils/ast-helpers.js';
|
||||
|
|
@ -242,7 +244,7 @@ export const swiftProvider = defineLanguage({
|
|||
treeSitterQueries: SWIFT_QUERIES,
|
||||
typeConfig: swiftConfig,
|
||||
exportChecker: swiftExportChecker,
|
||||
importResolver: resolveSwiftImport,
|
||||
importResolver: createImportResolver(swiftImportConfig),
|
||||
importSemantics: 'wildcard-leaf',
|
||||
heritageDefaultEdge: 'IMPLEMENTS',
|
||||
callExtractor: createCallExtractor(swiftCallConfig),
|
||||
|
|
|
|||
|
|
@ -19,6 +19,11 @@ import type { SyntaxNode } from '../utils/ast-helpers.js';
|
|||
import { typeConfig as typescriptConfig } from '../type-extractors/typescript.js';
|
||||
import { tsExportChecker } from '../export-detection.js';
|
||||
import { resolveTypescriptImport, resolveJavascriptImport } from '../import-resolvers/standard.js';
|
||||
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
|
||||
import {
|
||||
typescriptImportConfig,
|
||||
javascriptImportConfig,
|
||||
} from '../import-resolvers/configs/typescript-javascript.js';
|
||||
import { extractTsNamedBindings } from '../named-bindings/typescript.js';
|
||||
import { TYPESCRIPT_QUERIES, JAVASCRIPT_QUERIES } from '../tree-sitter-queries.js';
|
||||
import { typescriptFieldExtractor } from '../field-extractors/typescript.js';
|
||||
|
|
@ -168,7 +173,7 @@ export const typescriptProvider = defineLanguage({
|
|||
treeSitterQueries: TYPESCRIPT_QUERIES,
|
||||
typeConfig: typescriptConfig,
|
||||
exportChecker: tsExportChecker,
|
||||
importResolver: resolveTypescriptImport,
|
||||
importResolver: createImportResolver(typescriptImportConfig),
|
||||
namedBindingExtractor: extractTsNamedBindings,
|
||||
callExtractor: createCallExtractor(typescriptCallConfig),
|
||||
fieldExtractor: typescriptFieldExtractor,
|
||||
|
|
@ -187,7 +192,7 @@ export const javascriptProvider = defineLanguage({
|
|||
treeSitterQueries: JAVASCRIPT_QUERIES,
|
||||
typeConfig: typescriptConfig,
|
||||
exportChecker: tsExportChecker,
|
||||
importResolver: resolveJavascriptImport,
|
||||
importResolver: createImportResolver(javascriptImportConfig),
|
||||
namedBindingExtractor: extractTsNamedBindings,
|
||||
callExtractor: createCallExtractor(javascriptCallConfig),
|
||||
fieldExtractor: createFieldExtractor(javascriptConfig),
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import { defineLanguage } from '../language-provider.js';
|
|||
import { typeConfig as typescriptConfig } from '../type-extractors/typescript.js';
|
||||
import { tsExportChecker } from '../export-detection.js';
|
||||
import { resolveVueImport } from '../import-resolvers/vue.js';
|
||||
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
|
||||
import { vueImportConfig } from '../import-resolvers/configs/typescript-javascript.js';
|
||||
import { extractTsNamedBindings } from '../named-bindings/typescript.js';
|
||||
import { TYPESCRIPT_QUERIES } from '../tree-sitter-queries.js';
|
||||
import { typescriptFieldExtractor } from '../field-extractors/typescript.js';
|
||||
|
|
@ -69,7 +71,7 @@ export const vueProvider = defineLanguage({
|
|||
treeSitterQueries: TYPESCRIPT_QUERIES,
|
||||
typeConfig: typescriptConfig,
|
||||
exportChecker: tsExportChecker,
|
||||
importResolver: resolveVueImport,
|
||||
importResolver: createImportResolver(vueImportConfig),
|
||||
namedBindingExtractor: extractTsNamedBindings,
|
||||
callExtractor: createCallExtractor(typescriptCallConfig),
|
||||
fieldExtractor: typescriptFieldExtractor,
|
||||
|
|
|
|||
293
gitnexus/test/unit/import-resolver-factory.test.ts
Normal file
293
gitnexus/test/unit/import-resolver-factory.test.ts
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
/**
|
||||
* Unit tests for the import resolver factory and per-strategy composition.
|
||||
*
|
||||
* Validates that:
|
||||
* - createImportResolver chains strategies in order
|
||||
* - First non-null result wins
|
||||
* - Empty files array stops the chain (absorbing sentinel)
|
||||
* - All per-language configs produce valid resolvers
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { createImportResolver } from '../../src/core/ingestion/import-resolvers/resolver-factory.js';
|
||||
import { createStandardStrategy } from '../../src/core/ingestion/import-resolvers/standard.js';
|
||||
import type {
|
||||
ImportResolutionConfig,
|
||||
ImportResolverStrategy,
|
||||
ResolveCtx,
|
||||
} from '../../src/core/ingestion/import-resolvers/types.js';
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import { buildSuffixIndex } from '../../src/core/ingestion/import-resolvers/utils.js';
|
||||
|
||||
// ── Per-language strategy imports ──────────────────────────────────────
|
||||
import { goPackageStrategy } from '../../src/core/ingestion/import-resolvers/go.js';
|
||||
import {
|
||||
javaJvmStrategy,
|
||||
kotlinJvmStrategy,
|
||||
} from '../../src/core/ingestion/import-resolvers/jvm.js';
|
||||
import { rustModuleStrategy } from '../../src/core/ingestion/import-resolvers/rust.js';
|
||||
import { pythonImportStrategy } from '../../src/core/ingestion/import-resolvers/python.js';
|
||||
import { csharpNamespaceStrategy } from '../../src/core/ingestion/import-resolvers/csharp.js';
|
||||
import { phpPsr4Strategy } from '../../src/core/ingestion/import-resolvers/php.js';
|
||||
import { swiftPackageStrategy } from '../../src/core/ingestion/import-resolvers/swift.js';
|
||||
import {
|
||||
dartPackageStrategy,
|
||||
dartRelativeStrategy,
|
||||
} from '../../src/core/ingestion/import-resolvers/dart.js';
|
||||
import { rubyRequireStrategy } from '../../src/core/ingestion/import-resolvers/ruby.js';
|
||||
|
||||
// ── Per-language config imports ────────────────────────────────────────
|
||||
import {
|
||||
typescriptImportConfig,
|
||||
javascriptImportConfig,
|
||||
vueImportConfig,
|
||||
} from '../../src/core/ingestion/import-resolvers/configs/typescript-javascript.js';
|
||||
import {
|
||||
cImportConfig,
|
||||
cppImportConfig,
|
||||
} from '../../src/core/ingestion/import-resolvers/configs/c-cpp.js';
|
||||
import { goImportConfig } from '../../src/core/ingestion/import-resolvers/configs/go.js';
|
||||
import {
|
||||
javaImportConfig,
|
||||
kotlinImportConfig,
|
||||
} from '../../src/core/ingestion/import-resolvers/configs/jvm.js';
|
||||
import { pythonImportConfig } from '../../src/core/ingestion/import-resolvers/configs/python.js';
|
||||
import { rustImportConfig } from '../../src/core/ingestion/import-resolvers/configs/rust.js';
|
||||
import { csharpImportConfig } from '../../src/core/ingestion/import-resolvers/configs/csharp.js';
|
||||
import { phpImportConfig } from '../../src/core/ingestion/import-resolvers/configs/php.js';
|
||||
import { swiftImportConfig } from '../../src/core/ingestion/import-resolvers/configs/swift.js';
|
||||
import { dartImportConfig } from '../../src/core/ingestion/import-resolvers/configs/dart.js';
|
||||
import { rubyImportConfig } from '../../src/core/ingestion/import-resolvers/configs/ruby.js';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function makeCtx(files: string[]): ResolveCtx {
|
||||
const allFileList = files;
|
||||
const normalizedFileList = files.map((p) => p.replace(/\\/g, '/'));
|
||||
const allFilePaths = new Set(allFileList);
|
||||
const index = buildSuffixIndex(normalizedFileList, allFileList);
|
||||
return {
|
||||
allFilePaths,
|
||||
allFileList,
|
||||
normalizedFileList,
|
||||
index,
|
||||
resolveCache: new Map(),
|
||||
configs: {
|
||||
tsconfigPaths: null,
|
||||
goModule: null,
|
||||
composerConfig: null,
|
||||
swiftPackageConfig: null,
|
||||
csharpConfigs: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// createImportResolver — factory behavior
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('createImportResolver', () => {
|
||||
it('returns null when no strategies match', () => {
|
||||
const config: ImportResolutionConfig = {
|
||||
language: SupportedLanguages.TypeScript,
|
||||
strategies: [],
|
||||
};
|
||||
const resolver = createImportResolver(config);
|
||||
expect(resolver('./foo', 'src/index.ts', makeCtx([]))).toBeNull();
|
||||
});
|
||||
|
||||
it('returns first non-null result from strategy chain', () => {
|
||||
const strategyA: ImportResolverStrategy = () => null;
|
||||
const strategyB: ImportResolverStrategy = () => ({ kind: 'files', files: ['b.ts'] });
|
||||
const strategyC: ImportResolverStrategy = () => ({ kind: 'files', files: ['c.ts'] });
|
||||
|
||||
const resolver = createImportResolver({
|
||||
language: SupportedLanguages.TypeScript,
|
||||
strategies: [strategyA, strategyB, strategyC],
|
||||
});
|
||||
|
||||
const result = resolver('./foo', 'src/index.ts', makeCtx(['b.ts', 'c.ts']));
|
||||
expect(result).toEqual({ kind: 'files', files: ['b.ts'] });
|
||||
});
|
||||
|
||||
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 resolver = createImportResolver({
|
||||
language: SupportedLanguages.TypeScript,
|
||||
strategies: [absorber, shouldNotRun],
|
||||
});
|
||||
|
||||
const result = resolver('./foo', 'src/index.ts', makeCtx([]));
|
||||
expect(result).toEqual({ kind: 'files', files: [] });
|
||||
});
|
||||
|
||||
it('passes correct arguments to strategies', () => {
|
||||
const ctx = makeCtx(['src/utils.ts']);
|
||||
const captured: { raw: string; fp: string }[] = [];
|
||||
|
||||
const spy: ImportResolverStrategy = (raw, fp) => {
|
||||
captured.push({ raw, fp });
|
||||
return null;
|
||||
};
|
||||
|
||||
const resolver = createImportResolver({
|
||||
language: SupportedLanguages.TypeScript,
|
||||
strategies: [spy],
|
||||
});
|
||||
|
||||
resolver('./utils', 'src/index.ts', ctx);
|
||||
expect(captured).toEqual([{ raw: './utils', fp: 'src/index.ts' }]);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Per-language strategies — exported and callable
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('per-language strategy exports', () => {
|
||||
it('goPackageStrategy is a function', () => {
|
||||
expect(typeof goPackageStrategy).toBe('function');
|
||||
});
|
||||
|
||||
it('javaJvmStrategy is a function', () => {
|
||||
expect(typeof javaJvmStrategy).toBe('function');
|
||||
});
|
||||
|
||||
it('kotlinJvmStrategy is a function', () => {
|
||||
expect(typeof kotlinJvmStrategy).toBe('function');
|
||||
});
|
||||
|
||||
it('rustModuleStrategy is a function', () => {
|
||||
expect(typeof rustModuleStrategy).toBe('function');
|
||||
});
|
||||
|
||||
it('pythonImportStrategy is a function', () => {
|
||||
expect(typeof pythonImportStrategy).toBe('function');
|
||||
});
|
||||
|
||||
it('csharpNamespaceStrategy is a function', () => {
|
||||
expect(typeof csharpNamespaceStrategy).toBe('function');
|
||||
});
|
||||
|
||||
it('phpPsr4Strategy is a function', () => {
|
||||
expect(typeof phpPsr4Strategy).toBe('function');
|
||||
});
|
||||
|
||||
it('swiftPackageStrategy is a function', () => {
|
||||
expect(typeof swiftPackageStrategy).toBe('function');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Per-language configs — all construct cleanly
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('per-language import configs', () => {
|
||||
const configs: { name: string; config: ImportResolutionConfig }[] = [
|
||||
{ name: 'TypeScript', config: typescriptImportConfig },
|
||||
{ name: 'JavaScript', config: javascriptImportConfig },
|
||||
{ name: 'Vue', config: vueImportConfig },
|
||||
{ name: 'C', config: cImportConfig },
|
||||
{ name: 'C++', config: cppImportConfig },
|
||||
{ name: 'Go', config: goImportConfig },
|
||||
{ name: 'Java', config: javaImportConfig },
|
||||
{ name: 'Kotlin', config: kotlinImportConfig },
|
||||
{ name: 'Python', config: pythonImportConfig },
|
||||
{ name: 'Rust', config: rustImportConfig },
|
||||
{ name: 'C#', config: csharpImportConfig },
|
||||
{ name: 'PHP', config: phpImportConfig },
|
||||
{ name: 'Swift', config: swiftImportConfig },
|
||||
{ name: 'Dart', config: dartImportConfig },
|
||||
{ name: 'Ruby', config: rubyImportConfig },
|
||||
];
|
||||
|
||||
for (const { name, config } of configs) {
|
||||
it(`${name} config has strategies and constructs a resolver`, () => {
|
||||
expect(config.strategies.length).toBeGreaterThan(0);
|
||||
expect(() => createImportResolver(config)).not.toThrow();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// createStandardStrategy — reusable across languages
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('createStandardStrategy', () => {
|
||||
it('creates a reusable strategy from a language', () => {
|
||||
const strategy = createStandardStrategy(SupportedLanguages.TypeScript);
|
||||
expect(typeof strategy).toBe('function');
|
||||
});
|
||||
|
||||
it('resolves relative imports', () => {
|
||||
const strategy = createStandardStrategy(SupportedLanguages.TypeScript);
|
||||
const ctx = makeCtx(['src/utils.ts']);
|
||||
const result = strategy('./utils', 'src/index.ts', ctx);
|
||||
expect(result).toEqual({ kind: 'files', files: ['src/utils.ts'] });
|
||||
});
|
||||
|
||||
it('returns null for unresolvable imports', () => {
|
||||
const strategy = createStandardStrategy(SupportedLanguages.TypeScript);
|
||||
const ctx = makeCtx([]);
|
||||
const result = strategy('./nonexistent', 'src/index.ts', ctx);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Python strategy — absorbs unresolved relative imports
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('pythonImportStrategy', () => {
|
||||
it('absorbs unresolved relative imports with empty-files sentinel', () => {
|
||||
const ctx = makeCtx([]);
|
||||
const result = pythonImportStrategy('.nonexistent', 'src/app.py', ctx);
|
||||
expect(result).toEqual({ kind: 'files', files: [] });
|
||||
});
|
||||
|
||||
it('returns null for non-relative imports (allows chain to continue)', () => {
|
||||
const ctx = makeCtx([]);
|
||||
const result = pythonImportStrategy('os', 'src/app.py', ctx);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Dart strategies — absorbs SDK / external package imports
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('dartPackageStrategy', () => {
|
||||
it('absorbs dart: SDK imports', () => {
|
||||
const ctx = makeCtx([]);
|
||||
const result = dartPackageStrategy("'dart:async'", 'lib/main.dart', ctx);
|
||||
expect(result).toEqual({ kind: 'files', files: [] });
|
||||
});
|
||||
|
||||
it('absorbs external package: imports', () => {
|
||||
const ctx = makeCtx([]);
|
||||
const result = dartPackageStrategy("'package:http/http.dart'", 'lib/main.dart', ctx);
|
||||
expect(result).toEqual({ kind: 'files', files: [] });
|
||||
});
|
||||
|
||||
it('returns null for relative imports (chains to dartRelativeStrategy)', () => {
|
||||
const ctx = makeCtx([]);
|
||||
const result = dartPackageStrategy("'models.dart'", 'lib/main.dart', ctx);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue