gitnexus 1.3.10 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -2
- package/dist/cli/ai-context.d.ts +2 -1
- package/dist/cli/ai-context.js +33 -6
- package/dist/cli/analyze.d.ts +2 -0
- package/dist/cli/analyze.js +20 -2
- package/dist/cli/index.js +2 -0
- package/dist/cli/setup.js +17 -19
- package/dist/cli/skill-gen.d.ts +26 -0
- package/dist/cli/skill-gen.js +549 -0
- package/dist/core/graph/types.d.ts +5 -2
- package/dist/core/ingestion/call-processor.d.ts +5 -5
- package/dist/core/ingestion/call-processor.js +173 -260
- package/dist/core/ingestion/constants.d.ts +16 -0
- package/dist/core/ingestion/constants.js +16 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +2 -1
- package/dist/core/ingestion/entry-point-scoring.js +81 -22
- package/dist/core/ingestion/export-detection.d.ts +18 -0
- package/dist/core/ingestion/export-detection.js +230 -0
- package/dist/core/ingestion/framework-detection.d.ts +5 -1
- package/dist/core/ingestion/framework-detection.js +39 -8
- package/dist/core/ingestion/heritage-processor.d.ts +13 -4
- package/dist/core/ingestion/heritage-processor.js +92 -28
- package/dist/core/ingestion/import-processor.d.ts +17 -19
- package/dist/core/ingestion/import-processor.js +170 -695
- package/dist/core/ingestion/language-config.d.ts +46 -0
- package/dist/core/ingestion/language-config.js +167 -0
- package/dist/core/ingestion/mro-processor.d.ts +45 -0
- package/dist/core/ingestion/mro-processor.js +369 -0
- package/dist/core/ingestion/named-binding-extraction.d.ts +61 -0
- package/dist/core/ingestion/named-binding-extraction.js +363 -0
- package/dist/core/ingestion/parsing-processor.d.ts +1 -10
- package/dist/core/ingestion/parsing-processor.js +41 -177
- package/dist/core/ingestion/pipeline.js +41 -26
- package/dist/core/ingestion/process-processor.js +2 -1
- package/dist/core/ingestion/resolvers/csharp.d.ts +22 -0
- package/dist/core/ingestion/resolvers/csharp.js +109 -0
- package/dist/core/ingestion/resolvers/go.d.ts +19 -0
- package/dist/core/ingestion/resolvers/go.js +42 -0
- package/dist/core/ingestion/resolvers/index.d.ts +16 -0
- package/dist/core/ingestion/resolvers/index.js +11 -0
- package/dist/core/ingestion/resolvers/jvm.d.ts +23 -0
- package/dist/core/ingestion/resolvers/jvm.js +87 -0
- package/dist/core/ingestion/resolvers/php.d.ts +15 -0
- package/dist/core/ingestion/resolvers/php.js +35 -0
- package/dist/core/ingestion/resolvers/rust.d.ts +15 -0
- package/dist/core/ingestion/resolvers/rust.js +73 -0
- package/dist/core/ingestion/resolvers/standard.d.ts +28 -0
- package/dist/core/ingestion/resolvers/standard.js +145 -0
- package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
- package/dist/core/ingestion/resolvers/utils.js +120 -0
- package/dist/core/ingestion/symbol-resolver.d.ts +32 -0
- package/dist/core/ingestion/symbol-resolver.js +83 -0
- package/dist/core/ingestion/symbol-table.d.ts +12 -1
- package/dist/core/ingestion/symbol-table.js +19 -12
- package/dist/core/ingestion/tree-sitter-queries.d.ts +11 -11
- package/dist/core/ingestion/tree-sitter-queries.js +114 -9
- package/dist/core/ingestion/type-env.d.ts +27 -0
- package/dist/core/ingestion/type-env.js +86 -0
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +60 -0
- package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/csharp.js +89 -0
- package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/go.js +105 -0
- package/dist/core/ingestion/type-extractors/index.d.ts +21 -0
- package/dist/core/ingestion/type-extractors/index.js +29 -0
- package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
- package/dist/core/ingestion/type-extractors/jvm.js +121 -0
- package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/php.js +31 -0
- package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/python.js +41 -0
- package/dist/core/ingestion/type-extractors/rust.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/rust.js +39 -0
- package/dist/core/ingestion/type-extractors/shared.d.ts +17 -0
- package/dist/core/ingestion/type-extractors/shared.js +97 -0
- package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/swift.js +43 -0
- package/dist/core/ingestion/type-extractors/types.d.ts +14 -0
- package/dist/core/ingestion/type-extractors/types.js +1 -0
- package/dist/core/ingestion/type-extractors/typescript.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/typescript.js +46 -0
- package/dist/core/ingestion/utils.d.ts +67 -0
- package/dist/core/ingestion/utils.js +691 -4
- package/dist/core/ingestion/workers/parse-worker.d.ts +20 -3
- package/dist/core/ingestion/workers/parse-worker.js +84 -345
- package/dist/core/ingestion/workers/worker-pool.js +8 -0
- package/dist/core/kuzu/csv-generator.js +19 -3
- package/dist/core/kuzu/kuzu-adapter.js +5 -2
- package/dist/core/kuzu/schema.d.ts +3 -3
- package/dist/core/kuzu/schema.js +16 -1
- package/dist/core/search/bm25-index.js +2 -1
- package/dist/mcp/core/kuzu-adapter.js +6 -18
- package/dist/mcp/tools.js +12 -3
- package/hooks/claude/gitnexus-hook.cjs +149 -66
- package/package.json +1 -1
- package/skills/gitnexus-cli.md +1 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createKnowledgeGraph } from '../graph/graph.js';
|
|
2
2
|
import { processStructure } from './structure-processor.js';
|
|
3
3
|
import { processParsing } from './parsing-processor.js';
|
|
4
|
-
import { processImports, processImportsFromExtracted, createImportMap, buildImportResolutionContext } from './import-processor.js';
|
|
4
|
+
import { processImports, processImportsFromExtracted, createImportMap, createPackageMap, createNamedImportMap, buildImportResolutionContext } from './import-processor.js';
|
|
5
5
|
import { processCalls, processCallsFromExtracted, processRoutesFromExtracted } from './call-processor.js';
|
|
6
6
|
import { processHeritage, processHeritageFromExtracted } from './heritage-processor.js';
|
|
7
|
+
import { computeMRO } from './mro-processor.js';
|
|
7
8
|
import { processCommunities } from './community-processor.js';
|
|
8
9
|
import { processProcesses } from './process-processor.js';
|
|
9
10
|
import { createSymbolTable } from './symbol-table.js';
|
|
@@ -12,6 +13,9 @@ import { walkRepositoryPaths, readFileContents } from './filesystem-walker.js';
|
|
|
12
13
|
import { getLanguageFromFilename } from './utils.js';
|
|
13
14
|
import { isLanguageAvailable } from '../tree-sitter/parser-loader.js';
|
|
14
15
|
import { createWorkerPool } from './workers/worker-pool.js';
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
15
19
|
const isDev = process.env.NODE_ENV === 'development';
|
|
16
20
|
/** Max bytes of source content to load per parse chunk. Each chunk's source +
|
|
17
21
|
* parsed ASTs + extracted records + worker serialization overhead all live in
|
|
@@ -25,6 +29,8 @@ export const runPipelineFromRepo = async (repoPath, onProgress) => {
|
|
|
25
29
|
const symbolTable = createSymbolTable();
|
|
26
30
|
let astCache = createASTCache(AST_CACHE_CAP);
|
|
27
31
|
const importMap = createImportMap();
|
|
32
|
+
const packageMap = createPackageMap();
|
|
33
|
+
const namedImportMap = createNamedImportMap();
|
|
28
34
|
const cleanup = () => {
|
|
29
35
|
astCache.clear();
|
|
30
36
|
symbolTable.clear();
|
|
@@ -124,11 +130,21 @@ export const runPipelineFromRepo = async (repoPath, onProgress) => {
|
|
|
124
130
|
// Create worker pool once, reuse across chunks
|
|
125
131
|
let workerPool;
|
|
126
132
|
try {
|
|
127
|
-
|
|
133
|
+
let workerUrl = new URL('./workers/parse-worker.js', import.meta.url);
|
|
134
|
+
// When running under vitest, import.meta.url points to src/ where no .js exists.
|
|
135
|
+
// Fall back to the compiled dist/ worker so the pool can spawn real worker threads.
|
|
136
|
+
const thisDir = fileURLToPath(new URL('.', import.meta.url));
|
|
137
|
+
if (!fs.existsSync(fileURLToPath(workerUrl))) {
|
|
138
|
+
const distWorker = path.resolve(thisDir, '..', '..', '..', 'dist', 'core', 'ingestion', 'workers', 'parse-worker.js');
|
|
139
|
+
if (fs.existsSync(distWorker)) {
|
|
140
|
+
workerUrl = pathToFileURL(distWorker);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
128
143
|
workerPool = createWorkerPool(workerUrl);
|
|
129
144
|
}
|
|
130
145
|
catch (err) {
|
|
131
|
-
|
|
146
|
+
if (isDev)
|
|
147
|
+
console.warn('Worker pool creation failed, using sequential fallback:', err.message);
|
|
132
148
|
}
|
|
133
149
|
let filesParsedSoFar = 0;
|
|
134
150
|
// AST cache sized for one chunk (sequential fallback uses it for import/call/heritage)
|
|
@@ -165,22 +181,18 @@ export const runPipelineFromRepo = async (repoPath, onProgress) => {
|
|
|
165
181
|
}, workerPool);
|
|
166
182
|
if (chunkWorkerData) {
|
|
167
183
|
// Imports
|
|
168
|
-
await processImportsFromExtracted(graph, allPathObjects, chunkWorkerData.imports, importMap, undefined, repoPath, importCtx);
|
|
169
|
-
// Calls — resolve
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
// Routes — resolve immediately (Laravel route→controller CALLS edges)
|
|
178
|
-
if (chunkWorkerData.routes && chunkWorkerData.routes.length > 0) {
|
|
179
|
-
await processRoutesFromExtracted(graph, chunkWorkerData.routes, symbolTable, importMap);
|
|
180
|
-
}
|
|
184
|
+
await processImportsFromExtracted(graph, allPathObjects, chunkWorkerData.imports, importMap, undefined, repoPath, importCtx, packageMap, namedImportMap);
|
|
185
|
+
// Calls + Heritage + Routes — resolve in parallel (no shared mutable state between them)
|
|
186
|
+
// This is safe because each writes disjoint relationship types into idempotent id-keyed Maps,
|
|
187
|
+
// and the single-threaded event loop prevents races between synchronous addRelationship calls.
|
|
188
|
+
await Promise.all([
|
|
189
|
+
processCallsFromExtracted(graph, chunkWorkerData.calls, symbolTable, importMap, packageMap, undefined, namedImportMap),
|
|
190
|
+
processHeritageFromExtracted(graph, chunkWorkerData.heritage, symbolTable, importMap, packageMap),
|
|
191
|
+
processRoutesFromExtracted(graph, chunkWorkerData.routes ?? [], symbolTable, importMap, packageMap),
|
|
192
|
+
]);
|
|
181
193
|
}
|
|
182
194
|
else {
|
|
183
|
-
await processImports(graph, chunkFiles, astCache, importMap, undefined, repoPath, allPaths);
|
|
195
|
+
await processImports(graph, chunkFiles, astCache, importMap, undefined, repoPath, allPaths, packageMap, namedImportMap);
|
|
184
196
|
sequentialChunkPaths.push(chunkPaths);
|
|
185
197
|
}
|
|
186
198
|
filesParsedSoFar += chunkFiles.length;
|
|
@@ -199,8 +211,8 @@ export const runPipelineFromRepo = async (repoPath, onProgress) => {
|
|
|
199
211
|
.filter(p => chunkContents.has(p))
|
|
200
212
|
.map(p => ({ path: p, content: chunkContents.get(p) }));
|
|
201
213
|
astCache = createASTCache(chunkFiles.length);
|
|
202
|
-
await processCalls(graph, chunkFiles, astCache, symbolTable, importMap);
|
|
203
|
-
await processHeritage(graph, chunkFiles, astCache, symbolTable);
|
|
214
|
+
await processCalls(graph, chunkFiles, astCache, symbolTable, importMap, packageMap, undefined, namedImportMap);
|
|
215
|
+
await processHeritage(graph, chunkFiles, astCache, symbolTable, importMap, packageMap);
|
|
204
216
|
astCache.clear();
|
|
205
217
|
}
|
|
206
218
|
// Free import resolution context — suffix index + resolve cache no longer needed
|
|
@@ -209,13 +221,16 @@ export const runPipelineFromRepo = async (repoPath, onProgress) => {
|
|
|
209
221
|
importCtx.resolveCache.clear();
|
|
210
222
|
importCtx.suffixIndex = null;
|
|
211
223
|
importCtx.normalizedFileList = null;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
|
|
224
|
+
// ── Phase 4.5: Method Resolution Order ──────────────────────────────
|
|
225
|
+
onProgress({
|
|
226
|
+
phase: 'parsing',
|
|
227
|
+
percent: 81,
|
|
228
|
+
message: 'Computing method resolution order...',
|
|
229
|
+
stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: graph.nodeCount },
|
|
230
|
+
});
|
|
231
|
+
const mroResult = computeMRO(graph);
|
|
232
|
+
if (isDev && mroResult.entries.length > 0) {
|
|
233
|
+
console.log(`🔀 MRO: ${mroResult.entries.length} classes analyzed, ${mroResult.ambiguityCount} ambiguities found, ${mroResult.overrideEdges} OVERRIDES edges`);
|
|
219
234
|
}
|
|
220
235
|
// ── Phase 5: Communities ───────────────────────────────────────────
|
|
221
236
|
onProgress({
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* Processes help agents understand how features work through the codebase.
|
|
11
11
|
*/
|
|
12
12
|
import { calculateEntryPointScore, isTestFile } from './entry-point-scoring.js';
|
|
13
|
+
import { SupportedLanguages } from '../../config/supported-languages.js';
|
|
13
14
|
const isDev = process.env.NODE_ENV === 'development';
|
|
14
15
|
const DEFAULT_CONFIG = {
|
|
15
16
|
maxTraceDepth: 10,
|
|
@@ -178,7 +179,7 @@ const findEntryPoints = (graph, reverseCallsEdges, callsEdges) => {
|
|
|
178
179
|
if (callees.length === 0)
|
|
179
180
|
continue;
|
|
180
181
|
// Calculate entry point score using new scoring system
|
|
181
|
-
const { score: baseScore, reasons } = calculateEntryPointScore(node.properties.name, node.properties.language
|
|
182
|
+
const { score: baseScore, reasons } = calculateEntryPointScore(node.properties.name, node.properties.language ?? SupportedLanguages.JavaScript, node.properties.isExported ?? false, callers.length, callees.length, filePath // Pass filePath for framework detection
|
|
182
183
|
);
|
|
183
184
|
let score = baseScore;
|
|
184
185
|
const astFrameworkMultiplier = node.properties.astFrameworkMultiplier ?? 1.0;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C# namespace import resolution.
|
|
3
|
+
* Handles using-directive resolution via .csproj root namespace stripping.
|
|
4
|
+
*/
|
|
5
|
+
import type { SuffixIndex } from './utils.js';
|
|
6
|
+
/** C# project config parsed from .csproj files */
|
|
7
|
+
export interface CSharpProjectConfig {
|
|
8
|
+
/** Root namespace from <RootNamespace> or assembly name (default: project directory name) */
|
|
9
|
+
rootNamespace: string;
|
|
10
|
+
/** Directory containing the .csproj file */
|
|
11
|
+
projectDir: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a C# using-directive import path to matching .cs files.
|
|
15
|
+
* Tries single-file match first, then directory match for namespace imports.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveCSharpImport(importPath: string, csharpConfigs: CSharpProjectConfig[], normalizedFileList: string[], allFileList: string[], index?: SuffixIndex): string[];
|
|
18
|
+
/**
|
|
19
|
+
* Compute the directory suffix for a C# namespace import (for PackageMap).
|
|
20
|
+
* Returns a suffix like "/ProjectDir/Models/" or null if no config matches.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveCSharpNamespaceDir(importPath: string, csharpConfigs: CSharpProjectConfig[]): string | null;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C# namespace import resolution.
|
|
3
|
+
* Handles using-directive resolution via .csproj root namespace stripping.
|
|
4
|
+
*/
|
|
5
|
+
import { suffixResolve } from './utils.js';
|
|
6
|
+
/**
|
|
7
|
+
* Resolve a C# using-directive import path to matching .cs files.
|
|
8
|
+
* Tries single-file match first, then directory match for namespace imports.
|
|
9
|
+
*/
|
|
10
|
+
export function resolveCSharpImport(importPath, csharpConfigs, normalizedFileList, allFileList, index) {
|
|
11
|
+
const namespacePath = importPath.replace(/\./g, '/');
|
|
12
|
+
const results = [];
|
|
13
|
+
for (const config of csharpConfigs) {
|
|
14
|
+
const nsPath = config.rootNamespace.replace(/\./g, '/');
|
|
15
|
+
let relative;
|
|
16
|
+
if (namespacePath.startsWith(nsPath + '/')) {
|
|
17
|
+
relative = namespacePath.slice(nsPath.length + 1);
|
|
18
|
+
}
|
|
19
|
+
else if (namespacePath === nsPath) {
|
|
20
|
+
// The import IS the root namespace — resolve to all .cs files in project root
|
|
21
|
+
relative = '';
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const dirPrefix = config.projectDir
|
|
27
|
+
? (relative ? config.projectDir + '/' + relative : config.projectDir)
|
|
28
|
+
: relative;
|
|
29
|
+
// 1. Try as single file: relative.cs (e.g., "Models/DlqMessage.cs")
|
|
30
|
+
if (relative) {
|
|
31
|
+
const candidate = dirPrefix + '.cs';
|
|
32
|
+
if (index) {
|
|
33
|
+
const result = index.get(candidate) || index.getInsensitive(candidate);
|
|
34
|
+
if (result)
|
|
35
|
+
return [result];
|
|
36
|
+
}
|
|
37
|
+
// Also try suffix match
|
|
38
|
+
const suffixResult = index?.get(relative + '.cs') || index?.getInsensitive(relative + '.cs');
|
|
39
|
+
if (suffixResult)
|
|
40
|
+
return [suffixResult];
|
|
41
|
+
}
|
|
42
|
+
// 2. Try as directory: all .cs files directly inside (namespace import)
|
|
43
|
+
if (index) {
|
|
44
|
+
const dirFiles = index.getFilesInDir(dirPrefix, '.cs');
|
|
45
|
+
for (const f of dirFiles) {
|
|
46
|
+
const normalized = f.replace(/\\/g, '/');
|
|
47
|
+
// Check it's a direct child by finding the dirPrefix and ensuring no deeper slashes
|
|
48
|
+
const prefixIdx = normalized.indexOf(dirPrefix + '/');
|
|
49
|
+
if (prefixIdx < 0)
|
|
50
|
+
continue;
|
|
51
|
+
const afterDir = normalized.substring(prefixIdx + dirPrefix.length + 1);
|
|
52
|
+
if (!afterDir.includes('/')) {
|
|
53
|
+
results.push(f);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (results.length > 0)
|
|
57
|
+
return results;
|
|
58
|
+
}
|
|
59
|
+
// 3. Linear scan fallback for directory matching
|
|
60
|
+
if (results.length === 0) {
|
|
61
|
+
const dirTrail = dirPrefix + '/';
|
|
62
|
+
for (let i = 0; i < normalizedFileList.length; i++) {
|
|
63
|
+
const normalized = normalizedFileList[i];
|
|
64
|
+
if (!normalized.endsWith('.cs'))
|
|
65
|
+
continue;
|
|
66
|
+
const prefixIdx = normalized.indexOf(dirTrail);
|
|
67
|
+
if (prefixIdx < 0)
|
|
68
|
+
continue;
|
|
69
|
+
const afterDir = normalized.substring(prefixIdx + dirTrail.length);
|
|
70
|
+
if (!afterDir.includes('/')) {
|
|
71
|
+
results.push(allFileList[i]);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (results.length > 0)
|
|
75
|
+
return results;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Fallback: suffix matching without namespace stripping (single file)
|
|
79
|
+
const pathParts = namespacePath.split('/').filter(Boolean);
|
|
80
|
+
const fallback = suffixResolve(pathParts, normalizedFileList, allFileList, index);
|
|
81
|
+
return fallback ? [fallback] : [];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Compute the directory suffix for a C# namespace import (for PackageMap).
|
|
85
|
+
* Returns a suffix like "/ProjectDir/Models/" or null if no config matches.
|
|
86
|
+
*/
|
|
87
|
+
export function resolveCSharpNamespaceDir(importPath, csharpConfigs) {
|
|
88
|
+
const namespacePath = importPath.replace(/\./g, '/');
|
|
89
|
+
for (const config of csharpConfigs) {
|
|
90
|
+
const nsPath = config.rootNamespace.replace(/\./g, '/');
|
|
91
|
+
let relative;
|
|
92
|
+
if (namespacePath.startsWith(nsPath + '/')) {
|
|
93
|
+
relative = namespacePath.slice(nsPath.length + 1);
|
|
94
|
+
}
|
|
95
|
+
else if (namespacePath === nsPath) {
|
|
96
|
+
relative = '';
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const dirPrefix = config.projectDir
|
|
102
|
+
? (relative ? config.projectDir + '/' + relative : config.projectDir)
|
|
103
|
+
: relative;
|
|
104
|
+
if (!dirPrefix)
|
|
105
|
+
continue;
|
|
106
|
+
return '/' + dirPrefix + '/';
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Go package import resolution.
|
|
3
|
+
* Handles Go module path-based package imports.
|
|
4
|
+
*/
|
|
5
|
+
/** Go module config parsed from go.mod */
|
|
6
|
+
export interface GoModuleConfig {
|
|
7
|
+
/** Module path (e.g., "github.com/user/repo") */
|
|
8
|
+
modulePath: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Extract the package directory suffix from a Go import path.
|
|
12
|
+
* Returns the suffix string (e.g., "/internal/auth/") or null if invalid.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveGoPackageDir(importPath: string, goModule: GoModuleConfig): string | null;
|
|
15
|
+
/**
|
|
16
|
+
* Resolve a Go internal package import to all .go files in the package directory.
|
|
17
|
+
* Returns an array of file paths.
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveGoPackage(importPath: string, goModule: GoModuleConfig, normalizedFileList: string[], allFileList: string[]): string[];
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Go package import resolution.
|
|
3
|
+
* Handles Go module path-based package imports.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Extract the package directory suffix from a Go import path.
|
|
7
|
+
* Returns the suffix string (e.g., "/internal/auth/") or null if invalid.
|
|
8
|
+
*/
|
|
9
|
+
export function resolveGoPackageDir(importPath, goModule) {
|
|
10
|
+
if (!importPath.startsWith(goModule.modulePath))
|
|
11
|
+
return null;
|
|
12
|
+
const relativePkg = importPath.slice(goModule.modulePath.length + 1);
|
|
13
|
+
if (!relativePkg)
|
|
14
|
+
return null;
|
|
15
|
+
return '/' + relativePkg + '/';
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Resolve a Go internal package import to all .go files in the package directory.
|
|
19
|
+
* Returns an array of file paths.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveGoPackage(importPath, goModule, normalizedFileList, allFileList) {
|
|
22
|
+
if (!importPath.startsWith(goModule.modulePath))
|
|
23
|
+
return [];
|
|
24
|
+
// Strip module path to get relative package path
|
|
25
|
+
const relativePkg = importPath.slice(goModule.modulePath.length + 1); // e.g., "internal/auth"
|
|
26
|
+
if (!relativePkg)
|
|
27
|
+
return [];
|
|
28
|
+
const pkgSuffix = '/' + relativePkg + '/';
|
|
29
|
+
const matches = [];
|
|
30
|
+
for (let i = 0; i < normalizedFileList.length; i++) {
|
|
31
|
+
// Prepend '/' so paths like "internal/auth/service.go" match suffix "/internal/auth/"
|
|
32
|
+
const normalized = '/' + normalizedFileList[i];
|
|
33
|
+
// File must be directly in the package directory (not a subdirectory)
|
|
34
|
+
if (normalized.includes(pkgSuffix) && normalized.endsWith('.go') && !normalized.endsWith('_test.go')) {
|
|
35
|
+
const afterPkg = normalized.substring(normalized.indexOf(pkgSuffix) + pkgSuffix.length);
|
|
36
|
+
if (!afterPkg.includes('/')) {
|
|
37
|
+
matches.push(allFileList[i]);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return matches;
|
|
42
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language-specific import resolvers.
|
|
3
|
+
* Extracted from import-processor.ts for maintainability.
|
|
4
|
+
*/
|
|
5
|
+
export { EXTENSIONS, tryResolveWithExtensions, buildSuffixIndex, suffixResolve } from './utils.js';
|
|
6
|
+
export type { SuffixIndex } from './utils.js';
|
|
7
|
+
export { KOTLIN_EXTENSIONS, appendKotlinWildcard, resolveJvmWildcard, resolveJvmMemberImport } from './jvm.js';
|
|
8
|
+
export { resolveGoPackageDir, resolveGoPackage } from './go.js';
|
|
9
|
+
export type { GoModuleConfig } from './go.js';
|
|
10
|
+
export { resolveCSharpImport, resolveCSharpNamespaceDir } from './csharp.js';
|
|
11
|
+
export type { CSharpProjectConfig } from './csharp.js';
|
|
12
|
+
export { resolvePhpImport } from './php.js';
|
|
13
|
+
export type { ComposerConfig } from './php.js';
|
|
14
|
+
export { resolveRustImport, tryRustModulePath } from './rust.js';
|
|
15
|
+
export { resolveImportPath, RESOLVE_CACHE_CAP } from './standard.js';
|
|
16
|
+
export type { TsconfigPaths } from './standard.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language-specific import resolvers.
|
|
3
|
+
* Extracted from import-processor.ts for maintainability.
|
|
4
|
+
*/
|
|
5
|
+
export { EXTENSIONS, tryResolveWithExtensions, buildSuffixIndex, suffixResolve } from './utils.js';
|
|
6
|
+
export { KOTLIN_EXTENSIONS, appendKotlinWildcard, resolveJvmWildcard, resolveJvmMemberImport } from './jvm.js';
|
|
7
|
+
export { resolveGoPackageDir, resolveGoPackage } from './go.js';
|
|
8
|
+
export { resolveCSharpImport, resolveCSharpNamespaceDir } from './csharp.js';
|
|
9
|
+
export { resolvePhpImport } from './php.js';
|
|
10
|
+
export { resolveRustImport, tryRustModulePath } from './rust.js';
|
|
11
|
+
export { resolveImportPath, RESOLVE_CACHE_CAP } from './standard.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JVM import resolution (Java + Kotlin).
|
|
3
|
+
* Handles wildcard imports, member/static imports, and Kotlin-specific patterns.
|
|
4
|
+
*/
|
|
5
|
+
import type { SuffixIndex } from './utils.js';
|
|
6
|
+
/** Kotlin file extensions for JVM resolver reuse */
|
|
7
|
+
export declare const KOTLIN_EXTENSIONS: readonly string[];
|
|
8
|
+
/**
|
|
9
|
+
* Append .* to a Kotlin import path if the AST has a wildcard_import sibling node.
|
|
10
|
+
* Pure function — returns a new string without mutating the input.
|
|
11
|
+
*/
|
|
12
|
+
export declare const appendKotlinWildcard: (importPath: string, importNode: any) => string;
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a JVM wildcard import (com.example.*) to all matching files.
|
|
15
|
+
* Works for both Java (.java) and Kotlin (.kt, .kts).
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveJvmWildcard(importPath: string, normalizedFileList: string[], allFileList: string[], extensions: readonly string[], index?: SuffixIndex): string[];
|
|
18
|
+
/**
|
|
19
|
+
* Try to resolve a JVM member/static import by stripping the member name.
|
|
20
|
+
* Java: "com.example.Constants.VALUE" -> resolve "com.example.Constants"
|
|
21
|
+
* Kotlin: "com.example.Constants.VALUE" -> resolve "com.example.Constants"
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveJvmMemberImport(importPath: string, normalizedFileList: string[], allFileList: string[], extensions: readonly string[], index?: SuffixIndex): string | null;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JVM import resolution (Java + Kotlin).
|
|
3
|
+
* Handles wildcard imports, member/static imports, and Kotlin-specific patterns.
|
|
4
|
+
*/
|
|
5
|
+
/** Kotlin file extensions for JVM resolver reuse */
|
|
6
|
+
export const KOTLIN_EXTENSIONS = ['.kt', '.kts'];
|
|
7
|
+
/**
|
|
8
|
+
* Append .* to a Kotlin import path if the AST has a wildcard_import sibling node.
|
|
9
|
+
* Pure function — returns a new string without mutating the input.
|
|
10
|
+
*/
|
|
11
|
+
export const appendKotlinWildcard = (importPath, importNode) => {
|
|
12
|
+
for (let i = 0; i < importNode.childCount; i++) {
|
|
13
|
+
if (importNode.child(i)?.type === 'wildcard_import') {
|
|
14
|
+
return importPath.endsWith('.*') ? importPath : `${importPath}.*`;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return importPath;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a JVM wildcard import (com.example.*) to all matching files.
|
|
21
|
+
* Works for both Java (.java) and Kotlin (.kt, .kts).
|
|
22
|
+
*/
|
|
23
|
+
export function resolveJvmWildcard(importPath, normalizedFileList, allFileList, extensions, index) {
|
|
24
|
+
// "com.example.util.*" -> "com/example/util"
|
|
25
|
+
const packagePath = importPath.slice(0, -2).replace(/\./g, '/');
|
|
26
|
+
if (index) {
|
|
27
|
+
const candidates = extensions.flatMap(ext => index.getFilesInDir(packagePath, ext));
|
|
28
|
+
// Filter to only direct children (no subdirectories)
|
|
29
|
+
const packageSuffix = '/' + packagePath + '/';
|
|
30
|
+
return candidates.filter(f => {
|
|
31
|
+
const normalized = f.replace(/\\/g, '/');
|
|
32
|
+
const idx = normalized.indexOf(packageSuffix);
|
|
33
|
+
if (idx < 0)
|
|
34
|
+
return false;
|
|
35
|
+
const afterPkg = normalized.substring(idx + packageSuffix.length);
|
|
36
|
+
return !afterPkg.includes('/');
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// Fallback: linear scan
|
|
40
|
+
const packageSuffix = '/' + packagePath + '/';
|
|
41
|
+
const matches = [];
|
|
42
|
+
for (let i = 0; i < normalizedFileList.length; i++) {
|
|
43
|
+
const normalized = normalizedFileList[i];
|
|
44
|
+
if (normalized.includes(packageSuffix) &&
|
|
45
|
+
extensions.some(ext => normalized.endsWith(ext))) {
|
|
46
|
+
const afterPackage = normalized.substring(normalized.indexOf(packageSuffix) + packageSuffix.length);
|
|
47
|
+
if (!afterPackage.includes('/')) {
|
|
48
|
+
matches.push(allFileList[i]);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return matches;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Try to resolve a JVM member/static import by stripping the member name.
|
|
56
|
+
* Java: "com.example.Constants.VALUE" -> resolve "com.example.Constants"
|
|
57
|
+
* Kotlin: "com.example.Constants.VALUE" -> resolve "com.example.Constants"
|
|
58
|
+
*/
|
|
59
|
+
export function resolveJvmMemberImport(importPath, normalizedFileList, allFileList, extensions, index) {
|
|
60
|
+
// Member imports: com.example.Constants.VALUE or com.example.Constants.*
|
|
61
|
+
// The last segment is a member name if it starts with lowercase, is ALL_CAPS, or is a wildcard
|
|
62
|
+
const segments = importPath.split('.');
|
|
63
|
+
if (segments.length < 3)
|
|
64
|
+
return null;
|
|
65
|
+
const lastSeg = segments[segments.length - 1];
|
|
66
|
+
if (lastSeg === '*' || /^[a-z]/.test(lastSeg) || /^[A-Z_]+$/.test(lastSeg)) {
|
|
67
|
+
const classPath = segments.slice(0, -1).join('/');
|
|
68
|
+
for (const ext of extensions) {
|
|
69
|
+
const classSuffix = classPath + ext;
|
|
70
|
+
if (index) {
|
|
71
|
+
const result = index.get(classSuffix) || index.getInsensitive(classSuffix);
|
|
72
|
+
if (result)
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const fullSuffix = '/' + classSuffix;
|
|
77
|
+
for (let i = 0; i < normalizedFileList.length; i++) {
|
|
78
|
+
if (normalizedFileList[i].endsWith(fullSuffix) ||
|
|
79
|
+
normalizedFileList[i].toLowerCase().endsWith(fullSuffix.toLowerCase())) {
|
|
80
|
+
return allFileList[i];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHP PSR-4 import resolution.
|
|
3
|
+
* Handles use-statement resolution via composer.json autoload mappings.
|
|
4
|
+
*/
|
|
5
|
+
import type { SuffixIndex } from './utils.js';
|
|
6
|
+
/** PHP Composer PSR-4 autoload config */
|
|
7
|
+
export interface ComposerConfig {
|
|
8
|
+
/** Map of namespace prefix -> directory (e.g., "App\\" -> "app/") */
|
|
9
|
+
psr4: Map<string, string>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Resolve a PHP use-statement import path using PSR-4 mappings.
|
|
13
|
+
* e.g. "App\Http\Controllers\UserController" -> "app/Http/Controllers/UserController.php"
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolvePhpImport(importPath: string, composerConfig: ComposerConfig | null, allFiles: Set<string>, normalizedFileList: string[], allFileList: string[], index?: SuffixIndex): string | null;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHP PSR-4 import resolution.
|
|
3
|
+
* Handles use-statement resolution via composer.json autoload mappings.
|
|
4
|
+
*/
|
|
5
|
+
import { suffixResolve } from './utils.js';
|
|
6
|
+
/**
|
|
7
|
+
* Resolve a PHP use-statement import path using PSR-4 mappings.
|
|
8
|
+
* e.g. "App\Http\Controllers\UserController" -> "app/Http/Controllers/UserController.php"
|
|
9
|
+
*/
|
|
10
|
+
export function resolvePhpImport(importPath, composerConfig, allFiles, normalizedFileList, allFileList, index) {
|
|
11
|
+
// Normalize: replace backslashes with forward slashes
|
|
12
|
+
const normalized = importPath.replace(/\\/g, '/');
|
|
13
|
+
// Try PSR-4 resolution if composer.json was found
|
|
14
|
+
if (composerConfig) {
|
|
15
|
+
// Sort namespaces by length descending (longest match wins)
|
|
16
|
+
const sorted = [...composerConfig.psr4.entries()].sort((a, b) => b[0].length - a[0].length);
|
|
17
|
+
for (const [nsPrefix, dirPrefix] of sorted) {
|
|
18
|
+
const nsPrefixSlash = nsPrefix.replace(/\\/g, '/');
|
|
19
|
+
if (normalized.startsWith(nsPrefixSlash + '/') || normalized === nsPrefixSlash) {
|
|
20
|
+
const remainder = normalized.slice(nsPrefixSlash.length).replace(/^\//, '');
|
|
21
|
+
const filePath = dirPrefix + (remainder ? '/' + remainder : '') + '.php';
|
|
22
|
+
if (allFiles.has(filePath))
|
|
23
|
+
return filePath;
|
|
24
|
+
if (index) {
|
|
25
|
+
const result = index.getInsensitive(filePath);
|
|
26
|
+
if (result)
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Fallback: suffix matching (works without composer.json)
|
|
33
|
+
const pathParts = normalized.split('/').filter(Boolean);
|
|
34
|
+
return suffixResolve(pathParts, normalizedFileList, allFileList, index);
|
|
35
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rust module import resolution.
|
|
3
|
+
* Handles crate::, super::, self:: prefix paths and :: separators.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Resolve Rust use-path to a file.
|
|
7
|
+
* Handles crate::, super::, self:: prefixes and :: path separators.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveRustImport(currentFile: string, importPath: string, allFiles: Set<string>): string | null;
|
|
10
|
+
/**
|
|
11
|
+
* Try to resolve a Rust module path to a file.
|
|
12
|
+
* Tries: path.rs, path/mod.rs, and with the last segment stripped
|
|
13
|
+
* (last segment might be a symbol name, not a module).
|
|
14
|
+
*/
|
|
15
|
+
export declare function tryRustModulePath(modulePath: string, allFiles: Set<string>): string | null;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rust module import resolution.
|
|
3
|
+
* Handles crate::, super::, self:: prefix paths and :: separators.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Resolve Rust use-path to a file.
|
|
7
|
+
* Handles crate::, super::, self:: prefixes and :: path separators.
|
|
8
|
+
*/
|
|
9
|
+
export function resolveRustImport(currentFile, importPath, allFiles) {
|
|
10
|
+
let rustPath;
|
|
11
|
+
if (importPath.startsWith('crate::')) {
|
|
12
|
+
// crate:: resolves from src/ directory (standard Rust layout)
|
|
13
|
+
rustPath = importPath.slice(7).replace(/::/g, '/');
|
|
14
|
+
// Try from src/ (standard layout)
|
|
15
|
+
const fromSrc = tryRustModulePath('src/' + rustPath, allFiles);
|
|
16
|
+
if (fromSrc)
|
|
17
|
+
return fromSrc;
|
|
18
|
+
// Try from repo root (non-standard)
|
|
19
|
+
const fromRoot = tryRustModulePath(rustPath, allFiles);
|
|
20
|
+
if (fromRoot)
|
|
21
|
+
return fromRoot;
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
if (importPath.startsWith('super::')) {
|
|
25
|
+
// super:: = parent directory of current file's module
|
|
26
|
+
const currentDir = currentFile.split('/').slice(0, -1);
|
|
27
|
+
currentDir.pop(); // Go up one level for super::
|
|
28
|
+
rustPath = importPath.slice(7).replace(/::/g, '/');
|
|
29
|
+
const fullPath = [...currentDir, rustPath].join('/');
|
|
30
|
+
return tryRustModulePath(fullPath, allFiles);
|
|
31
|
+
}
|
|
32
|
+
if (importPath.startsWith('self::')) {
|
|
33
|
+
// self:: = current module's directory
|
|
34
|
+
const currentDir = currentFile.split('/').slice(0, -1);
|
|
35
|
+
rustPath = importPath.slice(6).replace(/::/g, '/');
|
|
36
|
+
const fullPath = [...currentDir, rustPath].join('/');
|
|
37
|
+
return tryRustModulePath(fullPath, allFiles);
|
|
38
|
+
}
|
|
39
|
+
// Bare path without prefix (e.g., from a use in a nested module)
|
|
40
|
+
// Convert :: to / and try suffix matching
|
|
41
|
+
if (importPath.includes('::')) {
|
|
42
|
+
rustPath = importPath.replace(/::/g, '/');
|
|
43
|
+
return tryRustModulePath(rustPath, allFiles);
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Try to resolve a Rust module path to a file.
|
|
49
|
+
* Tries: path.rs, path/mod.rs, and with the last segment stripped
|
|
50
|
+
* (last segment might be a symbol name, not a module).
|
|
51
|
+
*/
|
|
52
|
+
export function tryRustModulePath(modulePath, allFiles) {
|
|
53
|
+
// Try direct: path.rs
|
|
54
|
+
if (allFiles.has(modulePath + '.rs'))
|
|
55
|
+
return modulePath + '.rs';
|
|
56
|
+
// Try directory: path/mod.rs
|
|
57
|
+
if (allFiles.has(modulePath + '/mod.rs'))
|
|
58
|
+
return modulePath + '/mod.rs';
|
|
59
|
+
// Try path/lib.rs (for crate root)
|
|
60
|
+
if (allFiles.has(modulePath + '/lib.rs'))
|
|
61
|
+
return modulePath + '/lib.rs';
|
|
62
|
+
// The last segment might be a symbol (function, struct, etc.), not a module.
|
|
63
|
+
// Strip it and try again.
|
|
64
|
+
const lastSlash = modulePath.lastIndexOf('/');
|
|
65
|
+
if (lastSlash > 0) {
|
|
66
|
+
const parentPath = modulePath.substring(0, lastSlash);
|
|
67
|
+
if (allFiles.has(parentPath + '.rs'))
|
|
68
|
+
return parentPath + '.rs';
|
|
69
|
+
if (allFiles.has(parentPath + '/mod.rs'))
|
|
70
|
+
return parentPath + '/mod.rs';
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard import path resolution.
|
|
3
|
+
* Handles relative imports, path alias rewriting, and generic suffix matching.
|
|
4
|
+
* Used as the fallback when language-specific resolvers don't match.
|
|
5
|
+
*/
|
|
6
|
+
import type { SuffixIndex } from './utils.js';
|
|
7
|
+
import { SupportedLanguages } from '../../../config/supported-languages.js';
|
|
8
|
+
/** TypeScript path alias config parsed from tsconfig.json */
|
|
9
|
+
export interface TsconfigPaths {
|
|
10
|
+
/** Map of alias prefix -> target prefix (e.g., "@/" -> "src/") */
|
|
11
|
+
aliases: Map<string, string>;
|
|
12
|
+
/** Base URL for path resolution (relative to repo root) */
|
|
13
|
+
baseUrl: string;
|
|
14
|
+
}
|
|
15
|
+
/** Max entries in the resolve cache. Beyond this, entries are evicted.
|
|
16
|
+
* 100K entries ≈ 15MB — covers the most common import patterns. */
|
|
17
|
+
export declare const RESOLVE_CACHE_CAP = 100000;
|
|
18
|
+
/**
|
|
19
|
+
* Resolve an import path to a file path in the repository.
|
|
20
|
+
*
|
|
21
|
+
* Language-specific preprocessing is applied before the generic resolution:
|
|
22
|
+
* - TypeScript/JavaScript: rewrites tsconfig path aliases
|
|
23
|
+
* - Rust: converts crate::/super::/self:: to relative paths
|
|
24
|
+
*
|
|
25
|
+
* Java wildcards and Go package imports are handled separately in processImports
|
|
26
|
+
* because they resolve to multiple files.
|
|
27
|
+
*/
|
|
28
|
+
export declare const resolveImportPath: (currentFile: string, importPath: string, allFiles: Set<string>, allFileList: string[], normalizedFileList: string[], resolveCache: Map<string, string | null>, language: SupportedLanguages, tsconfigPaths: TsconfigPaths | null, index?: SuffixIndex) => string | null;
|