From 84a028f5f132840693d94322184a5ec31052f03c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Apr 2026 14:56:15 +0000 Subject: [PATCH] refactor: consolidate strategies into configs, remove legacy resolvers - Move all strategies from per-language files into their config files - Remove swift.ts and vue.ts (no shared helpers needed) - Remove legacy monolithic resolver functions from all per-language files - Remove unused legacy wrapper functions from standard.ts - Per-language files now only contain shared internal helpers - Fix lint warning in languages/php.ts (no-non-null-assertion) - Update test imports to reference configs/ instead of per-language files - All 3262+ tests pass, tsc --noEmit passes, zero lint errors Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/f8da6bc2-957c-4d20-87ba-402fa223c6c8 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> --- .../import-resolvers/configs/csharp.ts | 30 +++- .../import-resolvers/configs/dart.ts | 57 +++++++- .../ingestion/import-resolvers/configs/go.ts | 29 +++- .../ingestion/import-resolvers/configs/jvm.ts | 108 +++++++++++++- .../ingestion/import-resolvers/configs/php.ts | 21 ++- .../import-resolvers/configs/python.ts | 24 ++- .../import-resolvers/configs/ruby.ts | 15 +- .../import-resolvers/configs/rust.ts | 50 ++++++- .../import-resolvers/configs/swift.ts | 29 +++- .../core/ingestion/import-resolvers/csharp.ts | 45 +----- .../core/ingestion/import-resolvers/dart.ts | 68 +-------- .../src/core/ingestion/import-resolvers/go.ts | 44 +----- .../core/ingestion/import-resolvers/jvm.ts | 137 +----------------- .../core/ingestion/import-resolvers/php.ts | 33 +---- .../core/ingestion/import-resolvers/python.ts | 46 +----- .../core/ingestion/import-resolvers/ruby.ts | 31 +--- .../core/ingestion/import-resolvers/rust.ts | 66 +-------- .../ingestion/import-resolvers/standard.ts | 18 +-- .../core/ingestion/import-resolvers/swift.ts | 41 ------ .../core/ingestion/import-resolvers/vue.ts | 13 -- gitnexus/src/core/ingestion/languages/php.ts | 3 +- .../test/unit/import-resolver-factory.test.ts | 20 +-- 22 files changed, 394 insertions(+), 534 deletions(-) delete mode 100644 gitnexus/src/core/ingestion/import-resolvers/swift.ts delete mode 100644 gitnexus/src/core/ingestion/import-resolvers/vue.ts diff --git a/gitnexus/src/core/ingestion/import-resolvers/configs/csharp.ts b/gitnexus/src/core/ingestion/import-resolvers/configs/csharp.ts index 8fcb6f94d..04cc01815 100644 --- a/gitnexus/src/core/ingestion/import-resolvers/configs/csharp.ts +++ b/gitnexus/src/core/ingestion/import-resolvers/configs/csharp.ts @@ -4,9 +4,35 @@ */ import { SupportedLanguages } from 'gitnexus-shared'; -import type { ImportResolutionConfig } from '../types.js'; +import type { ImportResolutionConfig, ImportResolverStrategy } from '../types.js'; import { createStandardStrategy } from '../standard.js'; -import { csharpNamespaceStrategy } from '../csharp.js'; +import { resolveCSharpImportInternal, resolveCSharpNamespaceDir } from '../csharp.js'; + +/** 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( + rawImportPath, + csharpConfigs, + ctx.normalizedFileList, + ctx.allFileList, + ctx.index, + ); + if (resolvedFiles.length > 1) { + const dirSuffix = resolveCSharpNamespaceDir(rawImportPath, csharpConfigs); + if (dirSuffix) { + return { kind: 'package', files: resolvedFiles, dirSuffix }; + } + } + if (resolvedFiles.length > 0) return { kind: 'files', files: resolvedFiles }; + } + return null; +}; export const csharpImportConfig: ImportResolutionConfig = { language: SupportedLanguages.CSharp, diff --git a/gitnexus/src/core/ingestion/import-resolvers/configs/dart.ts b/gitnexus/src/core/ingestion/import-resolvers/configs/dart.ts index a8e5c4166..d72a787d7 100644 --- a/gitnexus/src/core/ingestion/import-resolvers/configs/dart.ts +++ b/gitnexus/src/core/ingestion/import-resolvers/configs/dart.ts @@ -4,8 +4,61 @@ */ import { SupportedLanguages } from 'gitnexus-shared'; -import type { ImportResolutionConfig } from '../types.js'; -import { dartPackageStrategy, dartRelativeStrategy } from '../dart.js'; +import type { ImportResolutionConfig, ImportResolverStrategy } from '../types.js'; +import { resolveStandard } from '../standard.js'; + +/** + * 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/ + 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 const dartImportConfig: ImportResolutionConfig = { language: SupportedLanguages.Dart, diff --git a/gitnexus/src/core/ingestion/import-resolvers/configs/go.ts b/gitnexus/src/core/ingestion/import-resolvers/configs/go.ts index 7b294159c..b22624d66 100644 --- a/gitnexus/src/core/ingestion/import-resolvers/configs/go.ts +++ b/gitnexus/src/core/ingestion/import-resolvers/configs/go.ts @@ -4,9 +4,34 @@ */ import { SupportedLanguages } from 'gitnexus-shared'; -import type { ImportResolutionConfig } from '../types.js'; +import type { ImportResolutionConfig, ImportResolverStrategy } from '../types.js'; import { createStandardStrategy } from '../standard.js'; -import { goPackageStrategy } from '../go.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, +) => { + const goModule = ctx.configs.goModule; + if (goModule && rawImportPath.startsWith(goModule.modulePath)) { + const pkgSuffix = resolveGoPackageDir(rawImportPath, goModule); + if (pkgSuffix) { + const pkgFiles = resolveGoPackage( + rawImportPath, + goModule, + ctx.normalizedFileList, + ctx.allFileList, + ); + if (pkgFiles.length > 0) { + return { kind: 'package', files: pkgFiles, dirSuffix: pkgSuffix }; + } + } + // Fall through if no files found (package might be external) + } + return null; +}; export const goImportConfig: ImportResolutionConfig = { language: SupportedLanguages.Go, diff --git a/gitnexus/src/core/ingestion/import-resolvers/configs/jvm.ts b/gitnexus/src/core/ingestion/import-resolvers/configs/jvm.ts index 67a3a7824..4b94d35b4 100644 --- a/gitnexus/src/core/ingestion/import-resolvers/configs/jvm.ts +++ b/gitnexus/src/core/ingestion/import-resolvers/configs/jvm.ts @@ -4,9 +4,113 @@ */ import { SupportedLanguages } from 'gitnexus-shared'; -import type { ImportResolutionConfig } from '../types.js'; +import type { ImportResolutionConfig, ImportResolverStrategy } from '../types.js'; import { createStandardStrategy } from '../standard.js'; -import { javaJvmStrategy, kotlinJvmStrategy } from '../jvm.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, +) => { + if (rawImportPath.endsWith('.*')) { + const matchedFiles = resolveJvmWildcard( + rawImportPath, + ctx.normalizedFileList, + ctx.allFileList, + ['.java'], + ctx.index, + ); + if (matchedFiles.length > 0) return { kind: 'files', files: matchedFiles }; + } else { + const memberResolved = resolveJvmMemberImport( + rawImportPath, + ctx.normalizedFileList, + ctx.allFileList, + ['.java'], + ctx.index, + ); + if (memberResolved) return { kind: 'files', files: [memberResolved] }; + } + return null; +}; + +/** + * 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, + ctx.normalizedFileList, + ctx.allFileList, + KOTLIN_EXTENSIONS, + ctx.index, + ); + if (matchedFiles.length === 0) { + const javaMatches = resolveJvmWildcard( + rawImportPath, + ctx.normalizedFileList, + ctx.allFileList, + ['.java'], + ctx.index, + ); + if (javaMatches.length > 0) return { kind: 'files', files: javaMatches }; + } + if (matchedFiles.length > 0) return { kind: 'files', files: matchedFiles }; + } else { + let memberResolved = resolveJvmMemberImport( + rawImportPath, + ctx.normalizedFileList, + ctx.allFileList, + KOTLIN_EXTENSIONS, + ctx.index, + ); + if (!memberResolved) { + memberResolved = resolveJvmMemberImport( + rawImportPath, + ctx.normalizedFileList, + ctx.allFileList, + ['.java'], + ctx.index, + ); + } + if (memberResolved) return { kind: 'files', files: [memberResolved] }; + + // Kotlin: top-level function imports (e.g. import models.getUser) have only 2 segments, + // which resolveJvmMemberImport skips (requires >=3). Fall back to package-directory scan + // for lowercase last segments (function/property imports). Uppercase last segments + // (class imports like models.User) fall through to standard suffix resolution. + const segments = rawImportPath.split('.'); + const lastSeg = segments[segments.length - 1]; + if (segments.length >= 2 && lastSeg[0] && lastSeg[0] === lastSeg[0].toLowerCase()) { + const pkgWildcard = segments.slice(0, -1).join('.') + '.*'; + let dirFiles = resolveJvmWildcard( + pkgWildcard, + ctx.normalizedFileList, + ctx.allFileList, + KOTLIN_EXTENSIONS, + ctx.index, + ); + if (dirFiles.length === 0) { + dirFiles = resolveJvmWildcard( + pkgWildcard, + ctx.normalizedFileList, + ctx.allFileList, + ['.java'], + ctx.index, + ); + } + if (dirFiles.length > 0) return { kind: 'files', files: dirFiles }; + } + } + return null; +}; export const javaImportConfig: ImportResolutionConfig = { language: SupportedLanguages.Java, diff --git a/gitnexus/src/core/ingestion/import-resolvers/configs/php.ts b/gitnexus/src/core/ingestion/import-resolvers/configs/php.ts index bd84ef34d..54b0557e1 100644 --- a/gitnexus/src/core/ingestion/import-resolvers/configs/php.ts +++ b/gitnexus/src/core/ingestion/import-resolvers/configs/php.ts @@ -4,8 +4,25 @@ */ import { SupportedLanguages } from 'gitnexus-shared'; -import type { ImportResolutionConfig } from '../types.js'; -import { phpPsr4Strategy } from '../php.js'; +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, +) => { + const resolved = resolvePhpImportInternal( + rawImportPath, + ctx.configs.composerConfig, + ctx.allFilePaths, + ctx.normalizedFileList, + ctx.allFileList, + ctx.index, + ); + return resolved ? { kind: 'files', files: [resolved] } : null; +}; export const phpImportConfig: ImportResolutionConfig = { language: SupportedLanguages.PHP, diff --git a/gitnexus/src/core/ingestion/import-resolvers/configs/python.ts b/gitnexus/src/core/ingestion/import-resolvers/configs/python.ts index bb7e87e64..5818c5448 100644 --- a/gitnexus/src/core/ingestion/import-resolvers/configs/python.ts +++ b/gitnexus/src/core/ingestion/import-resolvers/configs/python.ts @@ -4,9 +4,29 @@ */ import { SupportedLanguages } from 'gitnexus-shared'; -import type { ImportResolutionConfig } from '../types.js'; +import type { ImportResolutionConfig, ImportResolverStrategy } from '../types.js'; import { createStandardStrategy } from '../standard.js'; -import { pythonImportStrategy } from '../python.js'; +import { resolvePythonImportInternal } from '../python.js'; + +/** + * 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; +}; export const pythonImportConfig: ImportResolutionConfig = { language: SupportedLanguages.Python, diff --git a/gitnexus/src/core/ingestion/import-resolvers/configs/ruby.ts b/gitnexus/src/core/ingestion/import-resolvers/configs/ruby.ts index f02f27a7c..bacbbe0ea 100644 --- a/gitnexus/src/core/ingestion/import-resolvers/configs/ruby.ts +++ b/gitnexus/src/core/ingestion/import-resolvers/configs/ruby.ts @@ -4,8 +4,19 @@ */ import { SupportedLanguages } from 'gitnexus-shared'; -import type { ImportResolutionConfig } from '../types.js'; -import { rubyRequireStrategy } from '../ruby.js'; +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, +) => { + const pathParts = rawImportPath.replace(/^\.\//, '').split('/').filter(Boolean); + const resolved = suffixResolve(pathParts, ctx.normalizedFileList, ctx.allFileList, ctx.index); + return resolved ? { kind: 'files', files: [resolved] } : null; +}; export const rubyImportConfig: ImportResolutionConfig = { language: SupportedLanguages.Ruby, diff --git a/gitnexus/src/core/ingestion/import-resolvers/configs/rust.ts b/gitnexus/src/core/ingestion/import-resolvers/configs/rust.ts index ba9c9995c..b9c848c55 100644 --- a/gitnexus/src/core/ingestion/import-resolvers/configs/rust.ts +++ b/gitnexus/src/core/ingestion/import-resolvers/configs/rust.ts @@ -4,9 +4,55 @@ */ import { SupportedLanguages } from 'gitnexus-shared'; -import type { ImportResolutionConfig } from '../types.js'; +import type { ImportResolutionConfig, ImportResolverStrategy } from '../types.js'; import { createStandardStrategy } from '../standard.js'; -import { rustModuleStrategy } from '../rust.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, +) => { + // Top-level grouped: use {crate::a, crate::b} + if (rawImportPath.startsWith('{') && rawImportPath.endsWith('}')) { + const inner = rawImportPath.slice(1, -1); + const parts = inner + .split(',') + .map((p) => p.trim()) + .filter(Boolean); + const resolved: string[] = []; + for (const part of parts) { + const r = resolveRustImportInternal(filePath, part, ctx.allFilePaths); + if (r) resolved.push(r); + } + return resolved.length > 0 ? { kind: 'files', files: resolved } : null; + } + + // Scoped grouped: use crate::models::{User, Repo} + const braceIdx = rawImportPath.indexOf('::{'); + if (braceIdx !== -1 && rawImportPath.endsWith('}')) { + const pathPrefix = rawImportPath.substring(0, braceIdx); + const braceContent = rawImportPath.substring(braceIdx + 3, rawImportPath.length - 1); + const items = braceContent + .split(',') + .map((s) => s.trim()) + .filter(Boolean); + const resolved: string[] = []; + for (const item of items) { + // Handle `use crate::models::{User, Repo as R}` — strip alias for resolution + const itemName = item.includes(' as ') ? item.split(' as ')[0].trim() : item; + const r = resolveRustImportInternal(filePath, `${pathPrefix}::${itemName}`, ctx.allFilePaths); + if (r) resolved.push(r); + } + if (resolved.length > 0) return { kind: 'files', files: resolved }; + // Fallback: resolve the prefix path itself (e.g. crate::models -> models.rs) + const prefixResult = resolveRustImportInternal(filePath, pathPrefix, ctx.allFilePaths); + if (prefixResult) return { kind: 'files', files: [prefixResult] }; + } + + return null; +}; export const rustImportConfig: ImportResolutionConfig = { language: SupportedLanguages.Rust, diff --git a/gitnexus/src/core/ingestion/import-resolvers/configs/swift.ts b/gitnexus/src/core/ingestion/import-resolvers/configs/swift.ts index a1af57c64..3c26ad9d9 100644 --- a/gitnexus/src/core/ingestion/import-resolvers/configs/swift.ts +++ b/gitnexus/src/core/ingestion/import-resolvers/configs/swift.ts @@ -4,8 +4,33 @@ */ import { SupportedLanguages } from 'gitnexus-shared'; -import type { ImportResolutionConfig } from '../types.js'; -import { swiftPackageStrategy } from '../swift.js'; +import type { ImportResolutionConfig, ImportResolverStrategy } from '../types.js'; + +/** 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); + if (targetDir) { + const dirPrefix = targetDir + '/'; + const files: string[] = []; + for (let i = 0; i < ctx.normalizedFileList.length; i++) { + if ( + ctx.normalizedFileList[i].startsWith(dirPrefix) && + ctx.normalizedFileList[i].endsWith('.swift') + ) { + files.push(ctx.allFileList[i]); + } + } + if (files.length > 0) return { kind: 'files', files }; + } + } + return null; // External framework (Foundation, UIKit, etc.) +}; export const swiftImportConfig: ImportResolutionConfig = { language: SupportedLanguages.Swift, diff --git a/gitnexus/src/core/ingestion/import-resolvers/csharp.ts b/gitnexus/src/core/ingestion/import-resolvers/csharp.ts index fc815aab0..79548d7f6 100644 --- a/gitnexus/src/core/ingestion/import-resolvers/csharp.ts +++ b/gitnexus/src/core/ingestion/import-resolvers/csharp.ts @@ -1,13 +1,12 @@ /** - * C# namespace import resolution. - * Handles using-directive resolution via .csproj root namespace stripping. + * C# namespace import resolution — internal helpers. + * + * Strategy lives in configs/csharp.ts. + * This file contains shared helpers for namespace-based resolution. */ import type { SuffixIndex } from './utils.js'; import { suffixResolve } from './utils.js'; -import { SupportedLanguages } from 'gitnexus-shared'; -import type { ImportResult, ImportResolverStrategy, ResolveCtx } from './types.js'; -import { resolveStandard } from './standard.js'; import type { CSharpProjectConfig } from '../language-config.js'; /** @@ -126,39 +125,3 @@ export function resolveCSharpNamespaceDir( return null; } - -/** 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( - rawImportPath, - csharpConfigs, - ctx.normalizedFileList, - ctx.allFileList, - ctx.index, - ); - if (resolvedFiles.length > 1) { - const dirSuffix = resolveCSharpNamespaceDir(rawImportPath, csharpConfigs); - if (dirSuffix) { - return { kind: 'package', files: resolvedFiles, dirSuffix }; - } - } - if (resolvedFiles.length > 0) return { kind: 'files', files: resolvedFiles }; - } - 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); -} diff --git a/gitnexus/src/core/ingestion/import-resolvers/dart.ts b/gitnexus/src/core/ingestion/import-resolvers/dart.ts index 1a5bf8a9c..a830052a9 100644 --- a/gitnexus/src/core/ingestion/import-resolvers/dart.ts +++ b/gitnexus/src/core/ingestion/import-resolvers/dart.ts @@ -1,66 +1,19 @@ /** - * Dart import resolution. - * Handles package: imports (local packages) and relative imports. - * SDK imports (dart:*) and external packages are skipped. + * Dart import resolution — internal helpers. + * + * Strategies live in configs/dart.ts. + * This file is kept for backward compatibility with tests that import + * resolveDartImport directly. */ -import type { ImportResult, ImportResolverStrategy, ResolveCtx } from './types.js'; +import type { ImportResult, 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. + * Legacy monolithic Dart import resolver — kept for backward compatibility with + * existing tests. New code should use createImportResolver(dartImportConfig). */ -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/ - 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, @@ -93,11 +46,6 @@ export function resolveDartImport( } // Relative imports — use standard resolution. - // Dart relative imports don't require a leading "./" (e.g. `import 'models.dart'`). - // The standard resolver only recognises paths starting with "." as relative, so - // prepend "./" when the path doesn't already start with "." to ensure correct - // same-directory resolution (without this, "models.dart" would be mangled by the - // generic dot-to-slash conversion intended for Java-style package imports). const relPath = stripped.startsWith('.') ? stripped : './' + stripped; return resolveStandard(relPath, filePath, ctx, SupportedLanguages.Dart); } diff --git a/gitnexus/src/core/ingestion/import-resolvers/go.ts b/gitnexus/src/core/ingestion/import-resolvers/go.ts index c4fc5f945..c33c46422 100644 --- a/gitnexus/src/core/ingestion/import-resolvers/go.ts +++ b/gitnexus/src/core/ingestion/import-resolvers/go.ts @@ -1,11 +1,10 @@ /** - * Go package import resolution. - * Handles Go module path-based package imports. + * Go package import resolution — internal helpers. + * + * Strategy lives in configs/go.ts. + * This file contains the shared helpers used by the strategy. */ -import { SupportedLanguages } from 'gitnexus-shared'; -import type { ImportResult, ImportResolverStrategy, ResolveCtx } from './types.js'; -import { resolveStandard } from './standard.js'; import type { GoModuleConfig } from '../language-config.js'; /** @@ -56,38 +55,3 @@ export function resolveGoPackage( return matches; } - -/** 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); - if (pkgSuffix) { - const pkgFiles = resolveGoPackage( - rawImportPath, - goModule, - ctx.normalizedFileList, - ctx.allFileList, - ); - if (pkgFiles.length > 0) { - return { kind: 'package', files: pkgFiles, dirSuffix: pkgSuffix }; - } - } - // Fall through if no files found (package might be external) - } - 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); -} diff --git a/gitnexus/src/core/ingestion/import-resolvers/jvm.ts b/gitnexus/src/core/ingestion/import-resolvers/jvm.ts index 447cdf52d..194cfdac8 100644 --- a/gitnexus/src/core/ingestion/import-resolvers/jvm.ts +++ b/gitnexus/src/core/ingestion/import-resolvers/jvm.ts @@ -1,13 +1,13 @@ /** - * JVM import resolution (Java + Kotlin). - * Handles wildcard imports, member/static imports, and Kotlin-specific patterns. + * JVM import resolution — internal helpers (Java + Kotlin). + * + * Strategies live in configs/jvm.ts. + * This file contains shared helpers for wildcard/member resolution + * and the Kotlin wildcard preprocessor. */ import type { SuffixIndex } from './utils.js'; import type { SyntaxNode } from '../utils/ast-helpers.js'; -import { SupportedLanguages } from 'gitnexus-shared'; -import type { ImportResult, ImportResolverStrategy, ResolveCtx } from './types.js'; -import { resolveStandard } from './standard.js'; /** Kotlin file extensions for JVM resolver reuse */ export const KOTLIN_EXTENSIONS: readonly string[] = ['.kt', '.kts']; @@ -125,130 +125,3 @@ export function resolveJvmMemberImport( return null; } - -/** Java JVM resolution strategy — wildcard and member import resolution. */ -export const javaJvmStrategy: ImportResolverStrategy = ( - rawImportPath, - _filePath, - ctx, -) => { - if (rawImportPath.endsWith('.*')) { - const matchedFiles = resolveJvmWildcard( - rawImportPath, - ctx.normalizedFileList, - ctx.allFileList, - ['.java'], - ctx.index, - ); - if (matchedFiles.length > 0) return { kind: 'files', files: matchedFiles }; - } else { - const memberResolved = resolveJvmMemberImport( - rawImportPath, - ctx.normalizedFileList, - ctx.allFileList, - ['.java'], - ctx.index, - ); - if (memberResolved) return { kind: 'files', files: [memberResolved] }; - } - return null; -}; - -/** 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, - ctx.normalizedFileList, - ctx.allFileList, - KOTLIN_EXTENSIONS, - ctx.index, - ); - if (matchedFiles.length === 0) { - const javaMatches = resolveJvmWildcard( - rawImportPath, - ctx.normalizedFileList, - ctx.allFileList, - ['.java'], - ctx.index, - ); - if (javaMatches.length > 0) return { kind: 'files', files: javaMatches }; - } - if (matchedFiles.length > 0) return { kind: 'files', files: matchedFiles }; - } else { - let memberResolved = resolveJvmMemberImport( - rawImportPath, - ctx.normalizedFileList, - ctx.allFileList, - KOTLIN_EXTENSIONS, - ctx.index, - ); - if (!memberResolved) { - memberResolved = resolveJvmMemberImport( - rawImportPath, - ctx.normalizedFileList, - ctx.allFileList, - ['.java'], - ctx.index, - ); - } - if (memberResolved) return { kind: 'files', files: [memberResolved] }; - - // Kotlin: top-level function imports (e.g. import models.getUser) have only 2 segments, - // which resolveJvmMemberImport skips (requires >=3). Fall back to package-directory scan - // for lowercase last segments (function/property imports). Uppercase last segments - // (class imports like models.User) fall through to standard suffix resolution. - const segments = rawImportPath.split('.'); - const lastSeg = segments[segments.length - 1]; - if (segments.length >= 2 && lastSeg[0] && lastSeg[0] === lastSeg[0].toLowerCase()) { - const pkgWildcard = segments.slice(0, -1).join('.') + '.*'; - let dirFiles = resolveJvmWildcard( - pkgWildcard, - ctx.normalizedFileList, - ctx.allFileList, - KOTLIN_EXTENSIONS, - ctx.index, - ); - if (dirFiles.length === 0) { - dirFiles = resolveJvmWildcard( - pkgWildcard, - ctx.normalizedFileList, - ctx.allFileList, - ['.java'], - ctx.index, - ); - } - if (dirFiles.length > 0) return { kind: 'files', files: dirFiles }; - } - } - 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); -} diff --git a/gitnexus/src/core/ingestion/import-resolvers/php.ts b/gitnexus/src/core/ingestion/import-resolvers/php.ts index 4c9a5432f..303bf5546 100644 --- a/gitnexus/src/core/ingestion/import-resolvers/php.ts +++ b/gitnexus/src/core/ingestion/import-resolvers/php.ts @@ -1,11 +1,12 @@ /** - * PHP PSR-4 import resolution. - * Handles use-statement resolution via composer.json autoload mappings. + * PHP PSR-4 import resolution — internal helpers. + * + * Strategy lives in configs/php.ts. + * This file contains the shared helper for PSR-4 resolution via composer.json. */ import type { SuffixIndex } from './utils.js'; import { suffixResolve } from './utils.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). */ @@ -91,29 +92,3 @@ export function resolvePhpImportInternal( const pathParts = normalized.split('/').filter(Boolean); return suffixResolve(pathParts, normalizedFileList, allFileList, index); } - -/** PHP PSR-4 resolution strategy via composer.json autoload mappings. */ -export const phpPsr4Strategy: ImportResolverStrategy = ( - rawImportPath, - _filePath, - ctx, -) => { - const resolved = resolvePhpImportInternal( - rawImportPath, - ctx.configs.composerConfig, - ctx.allFilePaths, - ctx.normalizedFileList, - ctx.allFileList, - 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); -} diff --git a/gitnexus/src/core/ingestion/import-resolvers/python.ts b/gitnexus/src/core/ingestion/import-resolvers/python.ts index 8d81c88af..264103a09 100644 --- a/gitnexus/src/core/ingestion/import-resolvers/python.ts +++ b/gitnexus/src/core/ingestion/import-resolvers/python.ts @@ -1,12 +1,12 @@ /** * Python import resolution — PEP 328 relative imports and proximity-based bare imports. * Import system spec: PEP 302 (original), PEP 451 (current). + * + * Strategy lives in configs/python.ts. + * This file contains the shared internal helper used by the strategy and tests. */ import { tryResolveWithExtensions } from './utils.js'; -import { SupportedLanguages } from 'gitnexus-shared'; -import type { ImportResult, ImportResolverStrategy, ResolveCtx } from './types.js'; -import { resolveStandard } from './standard.js'; /** * Resolve a Python import to a file path (low-level helper). @@ -74,43 +74,3 @@ 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. - */ -export function resolvePythonImport( - rawImportPath: string, - filePath: string, - ctx: ResolveCtx, -): ImportResult { - const resolved = resolvePythonImportInternal(filePath, rawImportPath, ctx.allFilePaths); - if (resolved) { - // Store in resolveCache so other files importing the same module skip the - // ancestor walk. The cache key matches resolveStandard's convention. - ctx.resolveCache.set(`${filePath}::${rawImportPath}`, resolved); - return { kind: 'files', files: [resolved] }; - } - if (rawImportPath.startsWith('.')) return null; // relative but unresolved -- don't suffix-match - return resolveStandard(rawImportPath, filePath, ctx, SupportedLanguages.Python); -} diff --git a/gitnexus/src/core/ingestion/import-resolvers/ruby.ts b/gitnexus/src/core/ingestion/import-resolvers/ruby.ts index fa2508ae2..4bf47d31f 100644 --- a/gitnexus/src/core/ingestion/import-resolvers/ruby.ts +++ b/gitnexus/src/core/ingestion/import-resolvers/ruby.ts @@ -1,11 +1,12 @@ /** - * Ruby require/require_relative import resolution. - * Handles path resolution for Ruby's require and require_relative calls. + * Ruby require/require_relative import resolution — internal helpers. + * + * Strategy lives in configs/ruby.ts. + * This file only contains the low-level helper used by the strategy. */ import type { SuffixIndex } from './utils.js'; import { suffixResolve } from './utils.js'; -import type { ImportResult, ImportResolverStrategy, ResolveCtx } from './types.js'; /** * Resolve a Ruby require/require_relative path to a matching .rb file (low-level helper). @@ -22,27 +23,3 @@ export function resolveRubyImportInternal( const pathParts = importPath.replace(/^\.\//, '').split('/').filter(Boolean); return suffixResolve(pathParts, normalizedFileList, allFileList, index); } - -/** Ruby require/require_relative resolution strategy. */ -export const rubyRequireStrategy: ImportResolverStrategy = ( - rawImportPath, - _filePath, - ctx, -) => { - const resolved = resolveRubyImportInternal( - rawImportPath, - ctx.normalizedFileList, - ctx.allFileList, - 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); -} diff --git a/gitnexus/src/core/ingestion/import-resolvers/rust.ts b/gitnexus/src/core/ingestion/import-resolvers/rust.ts index 006d0b438..2d1dd864a 100644 --- a/gitnexus/src/core/ingestion/import-resolvers/rust.ts +++ b/gitnexus/src/core/ingestion/import-resolvers/rust.ts @@ -1,12 +1,10 @@ /** - * Rust module import resolution. - * Handles crate::, super::, self:: prefix paths and :: separators. + * Rust module import resolution — internal helpers. + * + * Strategy lives in configs/rust.ts. + * This file contains shared helpers used by the strategy and standard.ts. */ -import { SupportedLanguages } from 'gitnexus-shared'; -import type { ImportResult, ImportResolverStrategy, ResolveCtx } from './types.js'; -import { resolveStandard } from './standard.js'; - /** * Resolve Rust use-path to a file (low-level helper). * Handles crate::, super::, self:: prefixes and :: path separators. @@ -84,59 +82,3 @@ export function tryRustModulePath(modulePath: string, allFiles: Set): st return null; } - -/** 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); - const parts = inner - .split(',') - .map((p) => p.trim()) - .filter(Boolean); - const resolved: string[] = []; - for (const part of parts) { - const r = resolveRustImportInternal(filePath, part, ctx.allFilePaths); - if (r) resolved.push(r); - } - return resolved.length > 0 ? { kind: 'files', files: resolved } : null; - } - - // Scoped grouped: use crate::models::{User, Repo} - const braceIdx = rawImportPath.indexOf('::{'); - if (braceIdx !== -1 && rawImportPath.endsWith('}')) { - const pathPrefix = rawImportPath.substring(0, braceIdx); - const braceContent = rawImportPath.substring(braceIdx + 3, rawImportPath.length - 1); - const items = braceContent - .split(',') - .map((s) => s.trim()) - .filter(Boolean); - const resolved: string[] = []; - for (const item of items) { - // Handle `use crate::models::{User, Repo as R}` — strip alias for resolution - const itemName = item.includes(' as ') ? item.split(' as ')[0].trim() : item; - const r = resolveRustImportInternal(filePath, `${pathPrefix}::${itemName}`, ctx.allFilePaths); - if (r) resolved.push(r); - } - if (resolved.length > 0) return { kind: 'files', files: resolved }; - // Fallback: resolve the prefix path itself (e.g. crate::models -> models.rs) - const prefixResult = resolveRustImportInternal(filePath, pathPrefix, ctx.allFilePaths); - if (prefixResult) return { kind: 'files', files: [prefixResult] }; - } - - 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); -} diff --git a/gitnexus/src/core/ingestion/import-resolvers/standard.ts b/gitnexus/src/core/ingestion/import-resolvers/standard.ts index 7aac0e191..f8aae9625 100644 --- a/gitnexus/src/core/ingestion/import-resolvers/standard.ts +++ b/gitnexus/src/core/ingestion/import-resolvers/standard.ts @@ -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, ImportResolverStrategy, ResolveCtx } from './types.js'; +import type { ImportResult, ImportResolverStrategy, ResolveCtx } from './types.js'; import type { TsconfigPaths } from '../language-config.js'; /** Max entries in the resolve cache. Beyond this, entries are evicted. @@ -174,22 +174,6 @@ export function resolveStandard( return resolvedPath ? { kind: 'files', files: [resolvedPath] } : null; } -/** JavaScript: standard single-file resolution. */ -export const resolveJavascriptImport: ImportResolverFn = (raw, fp, ctx) => - resolveStandard(raw, fp, ctx, SupportedLanguages.JavaScript); - -/** TypeScript: standard single-file resolution. */ -export const resolveTypescriptImport: ImportResolverFn = (raw, fp, ctx) => - resolveStandard(raw, fp, ctx, SupportedLanguages.TypeScript); - -/** C: standard single-file resolution for #include directives. */ -export const resolveCImport: ImportResolverFn = (raw, fp, ctx) => - resolveStandard(raw, fp, ctx, SupportedLanguages.C); - -/** 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 // ============================================================================ diff --git a/gitnexus/src/core/ingestion/import-resolvers/swift.ts b/gitnexus/src/core/ingestion/import-resolvers/swift.ts deleted file mode 100644 index 24da4d2bd..000000000 --- a/gitnexus/src/core/ingestion/import-resolvers/swift.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Swift module import resolution. - * Handles module imports via Package.swift target map. - */ - -import type { ImportResult, ImportResolverStrategy, ResolveCtx } from './types.js'; - -/** 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); - if (targetDir) { - const dirPrefix = targetDir + '/'; - const files: string[] = []; - for (let i = 0; i < ctx.normalizedFileList.length; i++) { - if ( - ctx.normalizedFileList[i].startsWith(dirPrefix) && - ctx.normalizedFileList[i].endsWith('.swift') - ) { - files.push(ctx.allFileList[i]); - } - } - if (files.length > 0) return { kind: 'files', files }; - } - } - 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); -} diff --git a/gitnexus/src/core/ingestion/import-resolvers/vue.ts b/gitnexus/src/core/ingestion/import-resolvers/vue.ts deleted file mode 100644 index c46e3f725..000000000 --- a/gitnexus/src/core/ingestion/import-resolvers/vue.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Vue import resolver — delegates to TypeScript's standard resolver. - * - * Vue