gitnexus 1.3.11 → 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 +15 -6
- package/dist/cli/analyze.d.ts +2 -0
- package/dist/cli/analyze.js +12 -2
- package/dist/cli/index.js +2 -0
- 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 +26 -24
- 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/kuzu/csv-generator.js +19 -3
- package/dist/core/kuzu/kuzu-adapter.js +3 -0
- package/dist/core/kuzu/schema.d.ts +3 -3
- package/dist/core/kuzu/schema.js +16 -1
- package/dist/mcp/tools.js +12 -3
- package/package.json +1 -1
|
@@ -1,16 +1,29 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
1
|
import Parser from 'tree-sitter';
|
|
4
|
-
import { loadParser, loadLanguage } from '../tree-sitter/parser-loader.js';
|
|
2
|
+
import { isLanguageAvailable, loadParser, loadLanguage } from '../tree-sitter/parser-loader.js';
|
|
5
3
|
import { LANGUAGE_QUERIES } from './tree-sitter-queries.js';
|
|
6
4
|
import { generateId } from '../../lib/utils.js';
|
|
7
|
-
import { getLanguageFromFilename, yieldToEventLoop } from './utils.js';
|
|
5
|
+
import { getLanguageFromFilename, isVerboseIngestionEnabled, yieldToEventLoop } from './utils.js';
|
|
8
6
|
import { SupportedLanguages } from '../../config/supported-languages.js';
|
|
7
|
+
import { extractNamedBindings } from './named-binding-extraction.js';
|
|
8
|
+
import { getTreeSitterBufferSize } from './constants.js';
|
|
9
|
+
import { loadTsconfigPaths, loadGoModulePath, loadComposerConfig, loadCSharpProjectConfig, loadSwiftPackageConfig, } from './language-config.js';
|
|
10
|
+
import { buildSuffixIndex, resolveImportPath, appendKotlinWildcard, KOTLIN_EXTENSIONS, resolveJvmWildcard, resolveJvmMemberImport, resolveGoPackageDir, resolveGoPackage, resolveCSharpImport, resolveCSharpNamespaceDir, resolvePhpImport, resolveRustImport, } from './resolvers/index.js';
|
|
9
11
|
const isDev = process.env.NODE_ENV === 'development';
|
|
10
12
|
export const createImportMap = () => new Map();
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
export const createPackageMap = () => new Map();
|
|
14
|
+
export const createNamedImportMap = () => new Map();
|
|
15
|
+
/**
|
|
16
|
+
* Check if a file path is directly inside a package directory identified by its suffix.
|
|
17
|
+
* Used by the symbol resolver for Go and C# directory-level import matching.
|
|
18
|
+
*/
|
|
19
|
+
export function isFileInPackageDir(filePath, dirSuffix) {
|
|
20
|
+
// Prepend '/' so paths like "internal/auth/service.go" match suffix "/internal/auth/"
|
|
21
|
+
const normalized = '/' + filePath.replace(/\\/g, '/');
|
|
22
|
+
if (!normalized.includes(dirSuffix))
|
|
23
|
+
return false;
|
|
24
|
+
const afterDir = normalized.substring(normalized.indexOf(dirSuffix) + dirSuffix.length);
|
|
25
|
+
return !afterDir.includes('/');
|
|
26
|
+
}
|
|
14
27
|
export function buildImportResolutionContext(allPaths) {
|
|
15
28
|
const allFileList = allPaths;
|
|
16
29
|
const normalizedFileList = allFileList.map(p => p.replace(/\\/g, '/'));
|
|
@@ -19,550 +32,142 @@ export function buildImportResolutionContext(allPaths) {
|
|
|
19
32
|
return { allFilePaths, allFileList, normalizedFileList, suffixIndex, resolveCache: new Map() };
|
|
20
33
|
}
|
|
21
34
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
35
|
+
* Shared language dispatch for import resolution.
|
|
36
|
+
* Used by both processImports and processImportsFromExtracted.
|
|
24
37
|
*/
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const baseUrl = compilerOptions.baseUrl || '.';
|
|
38
|
-
const aliases = new Map();
|
|
39
|
-
for (const [pattern, targets] of Object.entries(compilerOptions.paths)) {
|
|
40
|
-
if (!Array.isArray(targets) || targets.length === 0)
|
|
41
|
-
continue;
|
|
42
|
-
const target = targets[0];
|
|
43
|
-
// Convert glob patterns: "@/*" -> "@/", "src/*" -> "src/"
|
|
44
|
-
const aliasPrefix = pattern.endsWith('/*') ? pattern.slice(0, -1) : pattern;
|
|
45
|
-
const targetPrefix = target.endsWith('/*') ? target.slice(0, -1) : target;
|
|
46
|
-
aliases.set(aliasPrefix, targetPrefix);
|
|
38
|
+
function resolveLanguageImport(filePath, rawImportPath, language, configs, ctx) {
|
|
39
|
+
const { allFilePaths, allFileList, normalizedFileList, index, resolveCache } = ctx;
|
|
40
|
+
const { tsconfigPaths, goModule, composerConfig, swiftPackageConfig, csharpConfigs } = configs;
|
|
41
|
+
// JVM languages (Java + Kotlin): handle wildcards and member imports
|
|
42
|
+
if (language === SupportedLanguages.Java || language === SupportedLanguages.Kotlin) {
|
|
43
|
+
const exts = language === SupportedLanguages.Java ? ['.java'] : KOTLIN_EXTENSIONS;
|
|
44
|
+
if (rawImportPath.endsWith('.*')) {
|
|
45
|
+
const matchedFiles = resolveJvmWildcard(rawImportPath, normalizedFileList, allFileList, exts, index);
|
|
46
|
+
if (matchedFiles.length === 0 && language === SupportedLanguages.Kotlin) {
|
|
47
|
+
const javaMatches = resolveJvmWildcard(rawImportPath, normalizedFileList, allFileList, ['.java'], index);
|
|
48
|
+
if (javaMatches.length > 0)
|
|
49
|
+
return { kind: 'files', files: javaMatches };
|
|
47
50
|
}
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
return { aliases, baseUrl };
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
// File doesn't exist or isn't valid JSON - try next
|
|
51
|
+
if (matchedFiles.length > 0)
|
|
52
|
+
return { kind: 'files', files: matchedFiles };
|
|
53
|
+
// Fall through to standard resolution
|
|
57
54
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
* Parse go.mod to extract module path.
|
|
63
|
-
*/
|
|
64
|
-
async function loadGoModulePath(repoRoot) {
|
|
65
|
-
try {
|
|
66
|
-
const goModPath = path.join(repoRoot, 'go.mod');
|
|
67
|
-
const content = await fs.readFile(goModPath, 'utf-8');
|
|
68
|
-
const match = content.match(/^module\s+(\S+)/m);
|
|
69
|
-
if (match) {
|
|
70
|
-
if (isDev) {
|
|
71
|
-
console.log(`📦 Loaded Go module path: ${match[1]}`);
|
|
72
|
-
}
|
|
73
|
-
return { modulePath: match[1] };
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
// No go.mod
|
|
78
|
-
}
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
async function loadComposerConfig(repoRoot) {
|
|
82
|
-
try {
|
|
83
|
-
const composerPath = path.join(repoRoot, 'composer.json');
|
|
84
|
-
const raw = await fs.readFile(composerPath, 'utf-8');
|
|
85
|
-
const composer = JSON.parse(raw);
|
|
86
|
-
const psr4Raw = composer.autoload?.['psr-4'] ?? {};
|
|
87
|
-
const psr4Dev = composer['autoload-dev']?.['psr-4'] ?? {};
|
|
88
|
-
const merged = { ...psr4Raw, ...psr4Dev };
|
|
89
|
-
const psr4 = new Map();
|
|
90
|
-
for (const [ns, dir] of Object.entries(merged)) {
|
|
91
|
-
const nsNorm = ns.replace(/\\+$/, '');
|
|
92
|
-
const dirNorm = dir.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
93
|
-
psr4.set(nsNorm, dirNorm);
|
|
94
|
-
}
|
|
95
|
-
if (isDev) {
|
|
96
|
-
console.log(`📦 Loaded ${psr4.size} PSR-4 mappings from composer.json`);
|
|
97
|
-
}
|
|
98
|
-
return { psr4 };
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
async function loadSwiftPackageConfig(repoRoot) {
|
|
105
|
-
// Swift imports are module-name based (e.g., `import SiuperModel`)
|
|
106
|
-
// SPM convention: Sources/<TargetName>/ or Package/Sources/<TargetName>/
|
|
107
|
-
// We scan for these directories to build a target map
|
|
108
|
-
const targets = new Map();
|
|
109
|
-
const sourceDirs = ['Sources', 'Package/Sources', 'src'];
|
|
110
|
-
for (const sourceDir of sourceDirs) {
|
|
111
|
-
try {
|
|
112
|
-
const fullPath = path.join(repoRoot, sourceDir);
|
|
113
|
-
const entries = await fs.readdir(fullPath, { withFileTypes: true });
|
|
114
|
-
for (const entry of entries) {
|
|
115
|
-
if (entry.isDirectory()) {
|
|
116
|
-
targets.set(entry.name, sourceDir + '/' + entry.name);
|
|
117
|
-
}
|
|
55
|
+
else {
|
|
56
|
+
let memberResolved = resolveJvmMemberImport(rawImportPath, normalizedFileList, allFileList, exts, index);
|
|
57
|
+
if (!memberResolved && language === SupportedLanguages.Kotlin) {
|
|
58
|
+
memberResolved = resolveJvmMemberImport(rawImportPath, normalizedFileList, allFileList, ['.java'], index);
|
|
118
59
|
}
|
|
60
|
+
if (memberResolved)
|
|
61
|
+
return { kind: 'files', files: [memberResolved] };
|
|
62
|
+
// Fall through to standard resolution
|
|
119
63
|
}
|
|
120
|
-
catch {
|
|
121
|
-
// Directory doesn't exist
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
if (targets.size > 0) {
|
|
125
|
-
if (isDev) {
|
|
126
|
-
console.log(`📦 Loaded ${targets.size} Swift package targets`);
|
|
127
|
-
}
|
|
128
|
-
return { targets };
|
|
129
|
-
}
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
// ============================================================================
|
|
133
|
-
// IMPORT PATH RESOLUTION
|
|
134
|
-
// ============================================================================
|
|
135
|
-
/** All file extensions to try during resolution */
|
|
136
|
-
const EXTENSIONS = [
|
|
137
|
-
'',
|
|
138
|
-
// TypeScript/JavaScript
|
|
139
|
-
'.tsx', '.ts', '.jsx', '.js', '/index.tsx', '/index.ts', '/index.jsx', '/index.js',
|
|
140
|
-
// Python
|
|
141
|
-
'.py', '/__init__.py',
|
|
142
|
-
// Java
|
|
143
|
-
'.java',
|
|
144
|
-
// Kotlin
|
|
145
|
-
'.kt', '.kts',
|
|
146
|
-
// C/C++
|
|
147
|
-
'.c', '.h', '.cpp', '.hpp', '.cc', '.cxx', '.hxx', '.hh',
|
|
148
|
-
// C#
|
|
149
|
-
'.cs',
|
|
150
|
-
// Go
|
|
151
|
-
'.go',
|
|
152
|
-
// Rust
|
|
153
|
-
'.rs', '/mod.rs',
|
|
154
|
-
// PHP
|
|
155
|
-
'.php', '.phtml',
|
|
156
|
-
// Swift
|
|
157
|
-
'.swift',
|
|
158
|
-
];
|
|
159
|
-
/**
|
|
160
|
-
* Try to match a path (with extensions) against the known file set.
|
|
161
|
-
* Returns the matched file path or null.
|
|
162
|
-
*/
|
|
163
|
-
function tryResolveWithExtensions(basePath, allFiles) {
|
|
164
|
-
for (const ext of EXTENSIONS) {
|
|
165
|
-
const candidate = basePath + ext;
|
|
166
|
-
if (allFiles.has(candidate))
|
|
167
|
-
return candidate;
|
|
168
64
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
// Map: directory suffix -> list of file paths in that directory
|
|
177
|
-
const dirMap = new Map();
|
|
178
|
-
for (let i = 0; i < normalizedFileList.length; i++) {
|
|
179
|
-
const normalized = normalizedFileList[i];
|
|
180
|
-
const original = allFileList[i];
|
|
181
|
-
const parts = normalized.split('/');
|
|
182
|
-
// Index all suffixes: "a/b/c.java" -> ["c.java", "b/c.java", "a/b/c.java"]
|
|
183
|
-
for (let j = parts.length - 1; j >= 0; j--) {
|
|
184
|
-
const suffix = parts.slice(j).join('/');
|
|
185
|
-
// Only store first match (longest path wins for ambiguous suffixes)
|
|
186
|
-
if (!exactMap.has(suffix)) {
|
|
187
|
-
exactMap.set(suffix, original);
|
|
188
|
-
}
|
|
189
|
-
const lower = suffix.toLowerCase();
|
|
190
|
-
if (!lowerMap.has(lower)) {
|
|
191
|
-
lowerMap.set(lower, original);
|
|
65
|
+
// Go: handle package-level imports
|
|
66
|
+
if (language === SupportedLanguages.Go && goModule && rawImportPath.startsWith(goModule.modulePath)) {
|
|
67
|
+
const pkgSuffix = resolveGoPackageDir(rawImportPath, goModule);
|
|
68
|
+
if (pkgSuffix) {
|
|
69
|
+
const pkgFiles = resolveGoPackage(rawImportPath, goModule, normalizedFileList, allFileList);
|
|
70
|
+
if (pkgFiles.length > 0) {
|
|
71
|
+
return { kind: 'package', files: pkgFiles, dirSuffix: pkgSuffix };
|
|
192
72
|
}
|
|
193
73
|
}
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const key = `${dirSuffix}:${ext}`;
|
|
204
|
-
let list = dirMap.get(key);
|
|
205
|
-
if (!list) {
|
|
206
|
-
list = [];
|
|
207
|
-
dirMap.set(key, list);
|
|
208
|
-
}
|
|
209
|
-
list.push(original);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return {
|
|
214
|
-
get: (suffix) => exactMap.get(suffix),
|
|
215
|
-
getInsensitive: (suffix) => lowerMap.get(suffix.toLowerCase()),
|
|
216
|
-
getFilesInDir: (dirSuffix, extension) => {
|
|
217
|
-
return dirMap.get(`${dirSuffix}:${extension}`) || [];
|
|
218
|
-
},
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* Suffix-based resolution using index. O(1) per lookup instead of O(files).
|
|
223
|
-
*/
|
|
224
|
-
function suffixResolve(pathParts, normalizedFileList, allFileList, index) {
|
|
225
|
-
if (index) {
|
|
226
|
-
for (let i = 0; i < pathParts.length; i++) {
|
|
227
|
-
const suffix = pathParts.slice(i).join('/');
|
|
228
|
-
for (const ext of EXTENSIONS) {
|
|
229
|
-
const suffixWithExt = suffix + ext;
|
|
230
|
-
const result = index.get(suffixWithExt) || index.getInsensitive(suffixWithExt);
|
|
231
|
-
if (result)
|
|
232
|
-
return result;
|
|
74
|
+
// Fall through if no files found (package might be external)
|
|
75
|
+
}
|
|
76
|
+
// C#: handle namespace-based imports (using directives)
|
|
77
|
+
if (language === SupportedLanguages.CSharp && csharpConfigs.length > 0) {
|
|
78
|
+
const resolvedFiles = resolveCSharpImport(rawImportPath, csharpConfigs, normalizedFileList, allFileList, index);
|
|
79
|
+
if (resolvedFiles.length > 1) {
|
|
80
|
+
const dirSuffix = resolveCSharpNamespaceDir(rawImportPath, csharpConfigs);
|
|
81
|
+
if (dirSuffix) {
|
|
82
|
+
return { kind: 'package', files: resolvedFiles, dirSuffix };
|
|
233
83
|
}
|
|
234
84
|
}
|
|
85
|
+
if (resolvedFiles.length > 0)
|
|
86
|
+
return { kind: 'files', files: resolvedFiles };
|
|
235
87
|
return null;
|
|
236
88
|
}
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
* Resolve an import path to a file path in the repository.
|
|
253
|
-
*
|
|
254
|
-
* Language-specific preprocessing is applied before the generic resolution:
|
|
255
|
-
* - TypeScript/JavaScript: rewrites tsconfig path aliases
|
|
256
|
-
* - Rust: converts crate::/super::/self:: to relative paths
|
|
257
|
-
*
|
|
258
|
-
* Java wildcards and Go package imports are handled separately in processImports
|
|
259
|
-
* because they resolve to multiple files.
|
|
260
|
-
*/
|
|
261
|
-
const resolveImportPath = (currentFile, importPath, allFiles, allFileList, normalizedFileList, resolveCache, language, tsconfigPaths, index) => {
|
|
262
|
-
const cacheKey = `${currentFile}::${importPath}`;
|
|
263
|
-
if (resolveCache.has(cacheKey))
|
|
264
|
-
return resolveCache.get(cacheKey) ?? null;
|
|
265
|
-
const cache = (result) => {
|
|
266
|
-
// Evict oldest 20% when cap is reached instead of clearing all
|
|
267
|
-
if (resolveCache.size >= RESOLVE_CACHE_CAP) {
|
|
268
|
-
const evictCount = Math.floor(RESOLVE_CACHE_CAP * 0.2);
|
|
269
|
-
const iter = resolveCache.keys();
|
|
270
|
-
for (let i = 0; i < evictCount; i++) {
|
|
271
|
-
const key = iter.next().value;
|
|
272
|
-
if (key !== undefined)
|
|
273
|
-
resolveCache.delete(key);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
resolveCache.set(cacheKey, result);
|
|
277
|
-
return result;
|
|
278
|
-
};
|
|
279
|
-
// ---- TypeScript/JavaScript: rewrite path aliases ----
|
|
280
|
-
if ((language === SupportedLanguages.TypeScript || language === SupportedLanguages.JavaScript) &&
|
|
281
|
-
tsconfigPaths &&
|
|
282
|
-
!importPath.startsWith('.')) {
|
|
283
|
-
for (const [aliasPrefix, targetPrefix] of tsconfigPaths.aliases) {
|
|
284
|
-
if (importPath.startsWith(aliasPrefix)) {
|
|
285
|
-
const remainder = importPath.slice(aliasPrefix.length);
|
|
286
|
-
// Build the rewritten path relative to baseUrl
|
|
287
|
-
const rewritten = tsconfigPaths.baseUrl === '.'
|
|
288
|
-
? targetPrefix + remainder
|
|
289
|
-
: tsconfigPaths.baseUrl + '/' + targetPrefix + remainder;
|
|
290
|
-
// Try direct resolution from repo root
|
|
291
|
-
const resolved = tryResolveWithExtensions(rewritten, allFiles);
|
|
292
|
-
if (resolved)
|
|
293
|
-
return cache(resolved);
|
|
294
|
-
// Try suffix matching as fallback
|
|
295
|
-
const parts = rewritten.split('/').filter(Boolean);
|
|
296
|
-
const suffixResult = suffixResolve(parts, normalizedFileList, allFileList, index);
|
|
297
|
-
if (suffixResult)
|
|
298
|
-
return cache(suffixResult);
|
|
89
|
+
// PHP: handle namespace-based imports (use statements)
|
|
90
|
+
if (language === SupportedLanguages.PHP) {
|
|
91
|
+
const resolved = resolvePhpImport(rawImportPath, composerConfig, allFilePaths, normalizedFileList, allFileList, index);
|
|
92
|
+
return resolved ? { kind: 'files', files: [resolved] } : null;
|
|
93
|
+
}
|
|
94
|
+
// Swift: handle module imports
|
|
95
|
+
if (language === SupportedLanguages.Swift && swiftPackageConfig) {
|
|
96
|
+
const targetDir = swiftPackageConfig.targets.get(rawImportPath);
|
|
97
|
+
if (targetDir) {
|
|
98
|
+
const dirPrefix = targetDir + '/';
|
|
99
|
+
const files = [];
|
|
100
|
+
for (let i = 0; i < normalizedFileList.length; i++) {
|
|
101
|
+
if (normalizedFileList[i].startsWith(dirPrefix) && normalizedFileList[i].endsWith('.swift')) {
|
|
102
|
+
files.push(allFileList[i]);
|
|
103
|
+
}
|
|
299
104
|
}
|
|
105
|
+
if (files.length > 0)
|
|
106
|
+
return { kind: 'files', files };
|
|
300
107
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
for (const part of parts) {
|
|
313
|
-
if (part === '.')
|
|
314
|
-
continue;
|
|
315
|
-
if (part === '..') {
|
|
316
|
-
currentDir.pop();
|
|
317
|
-
}
|
|
318
|
-
else {
|
|
319
|
-
currentDir.push(part);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
const basePath = currentDir.join('/');
|
|
323
|
-
if (importPath.startsWith('.')) {
|
|
324
|
-
const resolved = tryResolveWithExtensions(basePath, allFiles);
|
|
325
|
-
return cache(resolved);
|
|
326
|
-
}
|
|
327
|
-
// ---- Generic package/absolute import resolution (suffix matching) ----
|
|
328
|
-
// Java wildcards are handled in processImports, not here
|
|
329
|
-
if (importPath.endsWith('.*')) {
|
|
330
|
-
return cache(null);
|
|
331
|
-
}
|
|
332
|
-
const pathLike = importPath.includes('/')
|
|
333
|
-
? importPath
|
|
334
|
-
: importPath.replace(/\./g, '/');
|
|
335
|
-
const pathParts = pathLike.split('/').filter(Boolean);
|
|
336
|
-
const resolved = suffixResolve(pathParts, normalizedFileList, allFileList, index);
|
|
337
|
-
return cache(resolved);
|
|
338
|
-
};
|
|
339
|
-
// ============================================================================
|
|
340
|
-
// RUST MODULE RESOLUTION
|
|
341
|
-
// ============================================================================
|
|
342
|
-
/**
|
|
343
|
-
* Resolve Rust use-path to a file.
|
|
344
|
-
* Handles crate::, super::, self:: prefixes and :: path separators.
|
|
345
|
-
*/
|
|
346
|
-
function resolveRustImport(currentFile, importPath, allFiles) {
|
|
347
|
-
let rustPath;
|
|
348
|
-
if (importPath.startsWith('crate::')) {
|
|
349
|
-
// crate:: resolves from src/ directory (standard Rust layout)
|
|
350
|
-
rustPath = importPath.slice(7).replace(/::/g, '/');
|
|
351
|
-
// Try from src/ (standard layout)
|
|
352
|
-
const fromSrc = tryRustModulePath('src/' + rustPath, allFiles);
|
|
353
|
-
if (fromSrc)
|
|
354
|
-
return fromSrc;
|
|
355
|
-
// Try from repo root (non-standard)
|
|
356
|
-
const fromRoot = tryRustModulePath(rustPath, allFiles);
|
|
357
|
-
if (fromRoot)
|
|
358
|
-
return fromRoot;
|
|
359
|
-
return null;
|
|
360
|
-
}
|
|
361
|
-
if (importPath.startsWith('super::')) {
|
|
362
|
-
// super:: = parent directory of current file's module
|
|
363
|
-
const currentDir = currentFile.split('/').slice(0, -1);
|
|
364
|
-
currentDir.pop(); // Go up one level for super::
|
|
365
|
-
rustPath = importPath.slice(7).replace(/::/g, '/');
|
|
366
|
-
const fullPath = [...currentDir, rustPath].join('/');
|
|
367
|
-
return tryRustModulePath(fullPath, allFiles);
|
|
368
|
-
}
|
|
369
|
-
if (importPath.startsWith('self::')) {
|
|
370
|
-
// self:: = current module's directory
|
|
371
|
-
const currentDir = currentFile.split('/').slice(0, -1);
|
|
372
|
-
rustPath = importPath.slice(6).replace(/::/g, '/');
|
|
373
|
-
const fullPath = [...currentDir, rustPath].join('/');
|
|
374
|
-
return tryRustModulePath(fullPath, allFiles);
|
|
375
|
-
}
|
|
376
|
-
// Bare path without prefix (e.g., from a use in a nested module)
|
|
377
|
-
// Convert :: to / and try suffix matching
|
|
378
|
-
if (importPath.includes('::')) {
|
|
379
|
-
rustPath = importPath.replace(/::/g, '/');
|
|
380
|
-
return tryRustModulePath(rustPath, allFiles);
|
|
381
|
-
}
|
|
382
|
-
return null;
|
|
383
|
-
}
|
|
384
|
-
/**
|
|
385
|
-
* Try to resolve a Rust module path to a file.
|
|
386
|
-
* Tries: path.rs, path/mod.rs, and with the last segment stripped
|
|
387
|
-
* (last segment might be a symbol name, not a module).
|
|
388
|
-
*/
|
|
389
|
-
function tryRustModulePath(modulePath, allFiles) {
|
|
390
|
-
// Try direct: path.rs
|
|
391
|
-
if (allFiles.has(modulePath + '.rs'))
|
|
392
|
-
return modulePath + '.rs';
|
|
393
|
-
// Try directory: path/mod.rs
|
|
394
|
-
if (allFiles.has(modulePath + '/mod.rs'))
|
|
395
|
-
return modulePath + '/mod.rs';
|
|
396
|
-
// Try path/lib.rs (for crate root)
|
|
397
|
-
if (allFiles.has(modulePath + '/lib.rs'))
|
|
398
|
-
return modulePath + '/lib.rs';
|
|
399
|
-
// The last segment might be a symbol (function, struct, etc.), not a module.
|
|
400
|
-
// Strip it and try again.
|
|
401
|
-
const lastSlash = modulePath.lastIndexOf('/');
|
|
402
|
-
if (lastSlash > 0) {
|
|
403
|
-
const parentPath = modulePath.substring(0, lastSlash);
|
|
404
|
-
if (allFiles.has(parentPath + '.rs'))
|
|
405
|
-
return parentPath + '.rs';
|
|
406
|
-
if (allFiles.has(parentPath + '/mod.rs'))
|
|
407
|
-
return parentPath + '/mod.rs';
|
|
408
|
-
}
|
|
409
|
-
return null;
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* Append .* to a Kotlin import path if the AST has a wildcard_import sibling node.
|
|
413
|
-
* Pure function — returns a new string without mutating the input.
|
|
414
|
-
*/
|
|
415
|
-
const appendKotlinWildcard = (importPath, importNode) => {
|
|
416
|
-
for (let i = 0; i < importNode.childCount; i++) {
|
|
417
|
-
if (importNode.child(i)?.type === 'wildcard_import') {
|
|
418
|
-
return importPath.endsWith('.*') ? importPath : `${importPath}.*`;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
return importPath;
|
|
422
|
-
};
|
|
423
|
-
// ============================================================================
|
|
424
|
-
// JVM MULTI-FILE RESOLUTION (Java + Kotlin)
|
|
425
|
-
// ============================================================================
|
|
426
|
-
/** Kotlin file extensions for JVM resolver reuse */
|
|
427
|
-
const KOTLIN_EXTENSIONS = ['.kt', '.kts'];
|
|
428
|
-
/**
|
|
429
|
-
* Resolve a JVM wildcard import (com.example.*) to all matching files.
|
|
430
|
-
* Works for both Java (.java) and Kotlin (.kt, .kts).
|
|
431
|
-
*/
|
|
432
|
-
function resolveJvmWildcard(importPath, normalizedFileList, allFileList, extensions, index) {
|
|
433
|
-
// "com.example.util.*" -> "com/example/util"
|
|
434
|
-
const packagePath = importPath.slice(0, -2).replace(/\./g, '/');
|
|
435
|
-
if (index) {
|
|
436
|
-
const candidates = extensions.flatMap(ext => index.getFilesInDir(packagePath, ext));
|
|
437
|
-
// Filter to only direct children (no subdirectories)
|
|
438
|
-
const packageSuffix = '/' + packagePath + '/';
|
|
439
|
-
return candidates.filter(f => {
|
|
440
|
-
const normalized = f.replace(/\\/g, '/');
|
|
441
|
-
const idx = normalized.indexOf(packageSuffix);
|
|
442
|
-
if (idx < 0)
|
|
443
|
-
return false;
|
|
444
|
-
const afterPkg = normalized.substring(idx + packageSuffix.length);
|
|
445
|
-
return !afterPkg.includes('/');
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
// Fallback: linear scan
|
|
449
|
-
const packageSuffix = '/' + packagePath + '/';
|
|
450
|
-
const matches = [];
|
|
451
|
-
for (let i = 0; i < normalizedFileList.length; i++) {
|
|
452
|
-
const normalized = normalizedFileList[i];
|
|
453
|
-
if (normalized.includes(packageSuffix) &&
|
|
454
|
-
extensions.some(ext => normalized.endsWith(ext))) {
|
|
455
|
-
const afterPackage = normalized.substring(normalized.indexOf(packageSuffix) + packageSuffix.length);
|
|
456
|
-
if (!afterPackage.includes('/')) {
|
|
457
|
-
matches.push(allFileList[i]);
|
|
458
|
-
}
|
|
108
|
+
return null; // External framework (Foundation, UIKit, etc.)
|
|
109
|
+
}
|
|
110
|
+
// Rust: expand top-level grouped imports: use {crate::a, crate::b}
|
|
111
|
+
if (language === SupportedLanguages.Rust && rawImportPath.startsWith('{') && rawImportPath.endsWith('}')) {
|
|
112
|
+
const inner = rawImportPath.slice(1, -1);
|
|
113
|
+
const parts = inner.split(',').map(p => p.trim()).filter(Boolean);
|
|
114
|
+
const resolved = [];
|
|
115
|
+
for (const part of parts) {
|
|
116
|
+
const r = resolveRustImport(filePath, part, allFilePaths);
|
|
117
|
+
if (r)
|
|
118
|
+
resolved.push(r);
|
|
459
119
|
}
|
|
120
|
+
return resolved.length > 0 ? { kind: 'files', files: resolved } : null;
|
|
460
121
|
}
|
|
461
|
-
|
|
122
|
+
// Standard single-file resolution
|
|
123
|
+
const resolvedPath = resolveImportPath(filePath, rawImportPath, allFilePaths, allFileList, normalizedFileList, resolveCache, language, tsconfigPaths, index);
|
|
124
|
+
return resolvedPath ? { kind: 'files', files: [resolvedPath] } : null;
|
|
462
125
|
}
|
|
463
126
|
/**
|
|
464
|
-
*
|
|
465
|
-
*
|
|
466
|
-
*
|
|
127
|
+
* Apply an ImportResult: emit graph edges and update ImportMap/PackageMap.
|
|
128
|
+
* If namedBindings are provided and the import resolves to a single file,
|
|
129
|
+
* also populate the NamedImportMap for precise Tier 2a resolution.
|
|
467
130
|
*/
|
|
468
|
-
function
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
if (lastSeg === '*' || /^[a-z]/.test(lastSeg) || /^[A-Z_]+$/.test(lastSeg)) {
|
|
476
|
-
const classPath = segments.slice(0, -1).join('/');
|
|
477
|
-
for (const ext of extensions) {
|
|
478
|
-
const classSuffix = classPath + ext;
|
|
479
|
-
if (index) {
|
|
480
|
-
const result = index.get(classSuffix) || index.getInsensitive(classSuffix);
|
|
481
|
-
if (result)
|
|
482
|
-
return result;
|
|
483
|
-
}
|
|
484
|
-
else {
|
|
485
|
-
const fullSuffix = '/' + classSuffix;
|
|
486
|
-
for (let i = 0; i < normalizedFileList.length; i++) {
|
|
487
|
-
if (normalizedFileList[i].endsWith(fullSuffix) ||
|
|
488
|
-
normalizedFileList[i].toLowerCase().endsWith(fullSuffix.toLowerCase())) {
|
|
489
|
-
return allFileList[i];
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
131
|
+
function applyImportResult(result, filePath, importMap, packageMap, addImportEdge, addImportGraphEdge, namedBindings, namedImportMap) {
|
|
132
|
+
if (!result)
|
|
133
|
+
return;
|
|
134
|
+
if (result.kind === 'package' && packageMap) {
|
|
135
|
+
// Store directory suffix in PackageMap (skip ImportMap expansion)
|
|
136
|
+
for (const resolvedFile of result.files) {
|
|
137
|
+
addImportGraphEdge(filePath, resolvedFile);
|
|
493
138
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
//
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
*/
|
|
504
|
-
function resolveGoPackage(importPath, goModule, normalizedFileList, allFileList) {
|
|
505
|
-
if (!importPath.startsWith(goModule.modulePath))
|
|
506
|
-
return [];
|
|
507
|
-
// Strip module path to get relative package path
|
|
508
|
-
const relativePkg = importPath.slice(goModule.modulePath.length + 1); // e.g., "internal/auth"
|
|
509
|
-
if (!relativePkg)
|
|
510
|
-
return [];
|
|
511
|
-
const pkgSuffix = '/' + relativePkg + '/';
|
|
512
|
-
const matches = [];
|
|
513
|
-
for (let i = 0; i < normalizedFileList.length; i++) {
|
|
514
|
-
const normalized = normalizedFileList[i];
|
|
515
|
-
// File must be directly in the package directory (not a subdirectory)
|
|
516
|
-
if (normalized.includes(pkgSuffix) && normalized.endsWith('.go') && !normalized.endsWith('_test.go')) {
|
|
517
|
-
const afterPkg = normalized.substring(normalized.indexOf(pkgSuffix) + pkgSuffix.length);
|
|
518
|
-
if (!afterPkg.includes('/')) {
|
|
519
|
-
matches.push(allFileList[i]);
|
|
520
|
-
}
|
|
139
|
+
if (!packageMap.has(filePath))
|
|
140
|
+
packageMap.set(filePath, new Set());
|
|
141
|
+
packageMap.get(filePath).add(result.dirSuffix);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
// 'files' kind, or 'package' without PackageMap — use ImportMap directly
|
|
145
|
+
const files = result.files;
|
|
146
|
+
for (const resolvedFile of files) {
|
|
147
|
+
addImportEdge(filePath, resolvedFile);
|
|
521
148
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
* e.g. "App\Http\Controllers\UserController" -> "app/Http/Controllers/UserController.php"
|
|
531
|
-
*/
|
|
532
|
-
function resolvePhpImport(importPath, composerConfig, allFiles, normalizedFileList, allFileList, index) {
|
|
533
|
-
// Normalize: replace backslashes with forward slashes
|
|
534
|
-
const normalized = importPath.replace(/\\/g, '/');
|
|
535
|
-
// Try PSR-4 resolution if composer.json was found
|
|
536
|
-
if (composerConfig) {
|
|
537
|
-
// Sort namespaces by length descending (longest match wins)
|
|
538
|
-
const sorted = [...composerConfig.psr4.entries()].sort((a, b) => b[0].length - a[0].length);
|
|
539
|
-
for (const [nsPrefix, dirPrefix] of sorted) {
|
|
540
|
-
const nsPrefixSlash = nsPrefix.replace(/\\/g, '/');
|
|
541
|
-
if (normalized.startsWith(nsPrefixSlash + '/') || normalized === nsPrefixSlash) {
|
|
542
|
-
const remainder = normalized.slice(nsPrefixSlash.length).replace(/^\//, '');
|
|
543
|
-
const filePath = dirPrefix + (remainder ? '/' + remainder : '') + '.php';
|
|
544
|
-
if (allFiles.has(filePath))
|
|
545
|
-
return filePath;
|
|
546
|
-
if (index) {
|
|
547
|
-
const result = index.getInsensitive(filePath);
|
|
548
|
-
if (result)
|
|
549
|
-
return result;
|
|
550
|
-
}
|
|
149
|
+
// Record named bindings for precise Tier 2a resolution
|
|
150
|
+
if (namedBindings && namedImportMap && files.length === 1) {
|
|
151
|
+
const resolvedFile = files[0];
|
|
152
|
+
if (!namedImportMap.has(filePath))
|
|
153
|
+
namedImportMap.set(filePath, new Map());
|
|
154
|
+
const fileBindings = namedImportMap.get(filePath);
|
|
155
|
+
for (const binding of namedBindings) {
|
|
156
|
+
fileBindings.set(binding.local, { sourcePath: resolvedFile, exportedName: binding.exported });
|
|
551
157
|
}
|
|
552
158
|
}
|
|
553
159
|
}
|
|
554
|
-
// Fallback: suffix matching (works without composer.json)
|
|
555
|
-
const pathParts = normalized.split('/').filter(Boolean);
|
|
556
|
-
return suffixResolve(pathParts, normalizedFileList, allFileList, index);
|
|
557
160
|
}
|
|
558
161
|
// ============================================================================
|
|
559
162
|
// MAIN IMPORT PROCESSOR
|
|
560
163
|
// ============================================================================
|
|
561
|
-
export const processImports = async (graph, files, astCache, importMap, onProgress, repoRoot, allPaths) => {
|
|
164
|
+
export const processImports = async (graph, files, astCache, importMap, onProgress, repoRoot, allPaths, packageMap, namedImportMap) => {
|
|
562
165
|
// Use allPaths (full repo) when available for cross-chunk resolution, else fall back to chunk files
|
|
563
166
|
const allFileList = allPaths ?? files.map(f => f.path);
|
|
564
167
|
const allFilePaths = new Set(allFileList);
|
|
565
168
|
const parser = await loadParser();
|
|
169
|
+
const logSkipped = isVerboseIngestionEnabled();
|
|
170
|
+
const skippedByLang = logSkipped ? new Map() : null;
|
|
566
171
|
const resolveCache = new Map();
|
|
567
172
|
// Pre-compute normalized file list once (forward slashes)
|
|
568
173
|
const normalizedFileList = allFileList.map(p => p.replace(/\\/g, '/'));
|
|
@@ -573,12 +178,16 @@ export const processImports = async (graph, files, astCache, importMap, onProgre
|
|
|
573
178
|
let totalImportsResolved = 0;
|
|
574
179
|
// Load language-specific configs once before the file loop
|
|
575
180
|
const effectiveRoot = repoRoot || '';
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
181
|
+
const configs = {
|
|
182
|
+
tsconfigPaths: await loadTsconfigPaths(effectiveRoot),
|
|
183
|
+
goModule: await loadGoModulePath(effectiveRoot),
|
|
184
|
+
composerConfig: await loadComposerConfig(effectiveRoot),
|
|
185
|
+
swiftPackageConfig: await loadSwiftPackageConfig(effectiveRoot),
|
|
186
|
+
csharpConfigs: await loadCSharpProjectConfig(effectiveRoot),
|
|
187
|
+
};
|
|
188
|
+
const ctx = { allFilePaths, allFileList, normalizedFileList, index, resolveCache };
|
|
189
|
+
// Helper: add an IMPORTS edge to the graph only (no ImportMap update)
|
|
190
|
+
const addImportGraphEdge = (filePath, resolvedPath) => {
|
|
582
191
|
const sourceId = generateId('File', filePath);
|
|
583
192
|
const targetId = generateId('File', resolvedPath);
|
|
584
193
|
const relId = generateId('IMPORTS', `${filePath}->${resolvedPath}`);
|
|
@@ -591,6 +200,10 @@ export const processImports = async (graph, files, astCache, importMap, onProgre
|
|
|
591
200
|
confidence: 1.0,
|
|
592
201
|
reason: '',
|
|
593
202
|
});
|
|
203
|
+
};
|
|
204
|
+
// Helper: add an IMPORTS edge + update import map
|
|
205
|
+
const addImportEdge = (filePath, resolvedPath) => {
|
|
206
|
+
addImportGraphEdge(filePath, resolvedPath);
|
|
594
207
|
if (!importMap.has(filePath)) {
|
|
595
208
|
importMap.set(filePath, new Set());
|
|
596
209
|
}
|
|
@@ -605,6 +218,12 @@ export const processImports = async (graph, files, astCache, importMap, onProgre
|
|
|
605
218
|
const language = getLanguageFromFilename(file.path);
|
|
606
219
|
if (!language)
|
|
607
220
|
continue;
|
|
221
|
+
if (!isLanguageAvailable(language)) {
|
|
222
|
+
if (skippedByLang) {
|
|
223
|
+
skippedByLang.set(language, (skippedByLang.get(language) ?? 0) + 1);
|
|
224
|
+
}
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
608
227
|
const queryStr = LANGUAGE_QUERIES[language];
|
|
609
228
|
if (!queryStr)
|
|
610
229
|
continue;
|
|
@@ -615,7 +234,7 @@ export const processImports = async (graph, files, astCache, importMap, onProgre
|
|
|
615
234
|
let wasReparsed = false;
|
|
616
235
|
if (!tree) {
|
|
617
236
|
try {
|
|
618
|
-
tree = parser.parse(file.content, undefined, { bufferSize:
|
|
237
|
+
tree = parser.parse(file.content, undefined, { bufferSize: getTreeSitterBufferSize(file.content.length) });
|
|
619
238
|
}
|
|
620
239
|
catch (parseError) {
|
|
621
240
|
continue;
|
|
@@ -662,83 +281,18 @@ export const processImports = async (graph, files, astCache, importMap, onProgre
|
|
|
662
281
|
? appendKotlinWildcard(sourceNode.text.replace(/['"<>]/g, ''), captureMap['import'])
|
|
663
282
|
: sourceNode.text.replace(/['"<>]/g, '');
|
|
664
283
|
totalImportsFound++;
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
if (rawImportPath.endsWith('.*')) {
|
|
669
|
-
const matchedFiles = resolveJvmWildcard(rawImportPath, normalizedFileList, allFileList, exts, index);
|
|
670
|
-
// Kotlin can import Java files in mixed codebases — try .java as fallback
|
|
671
|
-
if (matchedFiles.length === 0 && language === SupportedLanguages.Kotlin) {
|
|
672
|
-
const javaMatches = resolveJvmWildcard(rawImportPath, normalizedFileList, allFileList, ['.java'], index);
|
|
673
|
-
for (const matchedFile of javaMatches) {
|
|
674
|
-
addImportEdge(file.path, matchedFile);
|
|
675
|
-
}
|
|
676
|
-
if (javaMatches.length > 0)
|
|
677
|
-
return;
|
|
678
|
-
}
|
|
679
|
-
for (const matchedFile of matchedFiles) {
|
|
680
|
-
addImportEdge(file.path, matchedFile);
|
|
681
|
-
}
|
|
682
|
-
return; // skip single-file resolution
|
|
683
|
-
}
|
|
684
|
-
// Try member/static import resolution (strip member name)
|
|
685
|
-
let memberResolved = resolveJvmMemberImport(rawImportPath, normalizedFileList, allFileList, exts, index);
|
|
686
|
-
// Kotlin can import Java files in mixed codebases — try .java as fallback
|
|
687
|
-
if (!memberResolved && language === SupportedLanguages.Kotlin) {
|
|
688
|
-
memberResolved = resolveJvmMemberImport(rawImportPath, normalizedFileList, allFileList, ['.java'], index);
|
|
689
|
-
}
|
|
690
|
-
if (memberResolved) {
|
|
691
|
-
addImportEdge(file.path, memberResolved);
|
|
692
|
-
return;
|
|
693
|
-
}
|
|
694
|
-
// Fall through to normal resolution for regular imports
|
|
695
|
-
}
|
|
696
|
-
// ---- Go: handle package-level imports ----
|
|
697
|
-
if (language === SupportedLanguages.Go && goModule && rawImportPath.startsWith(goModule.modulePath)) {
|
|
698
|
-
const pkgFiles = resolveGoPackage(rawImportPath, goModule, normalizedFileList, allFileList);
|
|
699
|
-
if (pkgFiles.length > 0) {
|
|
700
|
-
for (const pkgFile of pkgFiles) {
|
|
701
|
-
addImportEdge(file.path, pkgFile);
|
|
702
|
-
}
|
|
703
|
-
return; // skip single-file resolution
|
|
704
|
-
}
|
|
705
|
-
// Fall through if no files found (package might be external)
|
|
706
|
-
}
|
|
707
|
-
// ---- PHP: handle namespace-based imports (use statements) ----
|
|
708
|
-
if (language === SupportedLanguages.PHP) {
|
|
709
|
-
const resolved = resolvePhpImport(rawImportPath, composerConfig, allFilePaths, normalizedFileList, allFileList, index);
|
|
710
|
-
if (resolved) {
|
|
711
|
-
addImportEdge(file.path, resolved);
|
|
712
|
-
}
|
|
713
|
-
return;
|
|
714
|
-
}
|
|
715
|
-
// ---- Swift: handle module imports ----
|
|
716
|
-
if (language === SupportedLanguages.Swift && swiftPackageConfig) {
|
|
717
|
-
// Swift imports are module names: `import SiuperModel`
|
|
718
|
-
// Resolve to the module's source directory → all .swift files in it
|
|
719
|
-
const targetDir = swiftPackageConfig.targets.get(rawImportPath);
|
|
720
|
-
if (targetDir) {
|
|
721
|
-
// Find all .swift files in this target directory
|
|
722
|
-
const dirPrefix = targetDir + '/';
|
|
723
|
-
for (const filePath2 of allFileList) {
|
|
724
|
-
if (filePath2.startsWith(dirPrefix) && filePath2.endsWith('.swift')) {
|
|
725
|
-
addImportEdge(file.path, filePath2);
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
return;
|
|
729
|
-
}
|
|
730
|
-
// External framework (Foundation, UIKit, etc.) — skip
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
// ---- Standard single-file resolution ----
|
|
734
|
-
const resolvedPath = resolveImportPath(file.path, rawImportPath, allFilePaths, allFileList, normalizedFileList, resolveCache, language, tsconfigPaths, index);
|
|
735
|
-
if (resolvedPath) {
|
|
736
|
-
addImportEdge(file.path, resolvedPath);
|
|
737
|
-
}
|
|
284
|
+
const result = resolveLanguageImport(file.path, rawImportPath, language, configs, ctx);
|
|
285
|
+
const bindings = namedImportMap ? extractNamedBindings(captureMap['import'], language) : undefined;
|
|
286
|
+
applyImportResult(result, file.path, importMap, packageMap, addImportEdge, addImportGraphEdge, bindings, namedImportMap);
|
|
738
287
|
}
|
|
739
288
|
});
|
|
740
289
|
// Tree is now owned by the LRU cache — no manual delete needed
|
|
741
290
|
}
|
|
291
|
+
if (skippedByLang && skippedByLang.size > 0) {
|
|
292
|
+
for (const [lang, count] of skippedByLang.entries()) {
|
|
293
|
+
console.warn(`[ingestion] Skipped ${count} ${lang} file(s) in import processing — ${lang} parser not available.`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
742
296
|
if (isDev) {
|
|
743
297
|
console.log(`📊 Import processing complete: ${totalImportsResolved}/${totalImportsFound} imports resolved to graph edges`);
|
|
744
298
|
}
|
|
@@ -746,17 +300,22 @@ export const processImports = async (graph, files, astCache, importMap, onProgre
|
|
|
746
300
|
// ============================================================================
|
|
747
301
|
// FAST PATH: Resolve pre-extracted imports (no parsing needed)
|
|
748
302
|
// ============================================================================
|
|
749
|
-
export const processImportsFromExtracted = async (graph, files, extractedImports, importMap, onProgress, repoRoot, prebuiltCtx) => {
|
|
303
|
+
export const processImportsFromExtracted = async (graph, files, extractedImports, importMap, onProgress, repoRoot, prebuiltCtx, packageMap, namedImportMap) => {
|
|
750
304
|
const ctx = prebuiltCtx ?? buildImportResolutionContext(files.map(f => f.path));
|
|
751
305
|
const { allFilePaths, allFileList, normalizedFileList, suffixIndex: index, resolveCache } = ctx;
|
|
752
306
|
let totalImportsFound = 0;
|
|
753
307
|
let totalImportsResolved = 0;
|
|
754
308
|
const effectiveRoot = repoRoot || '';
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
309
|
+
const configs = {
|
|
310
|
+
tsconfigPaths: await loadTsconfigPaths(effectiveRoot),
|
|
311
|
+
goModule: await loadGoModulePath(effectiveRoot),
|
|
312
|
+
composerConfig: await loadComposerConfig(effectiveRoot),
|
|
313
|
+
swiftPackageConfig: await loadSwiftPackageConfig(effectiveRoot),
|
|
314
|
+
csharpConfigs: await loadCSharpProjectConfig(effectiveRoot),
|
|
315
|
+
};
|
|
316
|
+
const resolveCtx = { allFilePaths, allFileList, normalizedFileList, index, resolveCache };
|
|
317
|
+
// Helper: add an IMPORTS edge to the graph only (no ImportMap update)
|
|
318
|
+
const addImportGraphEdge = (filePath, resolvedPath) => {
|
|
760
319
|
const sourceId = generateId('File', filePath);
|
|
761
320
|
const targetId = generateId('File', resolvedPath);
|
|
762
321
|
const relId = generateId('IMPORTS', `${filePath}->${resolvedPath}`);
|
|
@@ -769,6 +328,9 @@ export const processImportsFromExtracted = async (graph, files, extractedImports
|
|
|
769
328
|
confidence: 1.0,
|
|
770
329
|
reason: '',
|
|
771
330
|
});
|
|
331
|
+
};
|
|
332
|
+
const addImportEdge = (filePath, resolvedPath) => {
|
|
333
|
+
addImportGraphEdge(filePath, resolvedPath);
|
|
772
334
|
if (!importMap.has(filePath)) {
|
|
773
335
|
importMap.set(filePath, new Set());
|
|
774
336
|
}
|
|
@@ -786,103 +348,16 @@ export const processImportsFromExtracted = async (graph, files, extractedImports
|
|
|
786
348
|
}
|
|
787
349
|
const totalFiles = importsByFile.size;
|
|
788
350
|
let filesProcessed = 0;
|
|
789
|
-
// Pre-build a suffix index for O(1) suffix lookups instead of O(n) linear scans
|
|
790
|
-
const suffixIndex = new Map();
|
|
791
|
-
for (let i = 0; i < normalizedFileList.length; i++) {
|
|
792
|
-
const normalized = normalizedFileList[i];
|
|
793
|
-
// Index by last path segment (filename) for fast suffix matching
|
|
794
|
-
const lastSlash = normalized.lastIndexOf('/');
|
|
795
|
-
const filename = lastSlash >= 0 ? normalized.substring(lastSlash + 1) : normalized;
|
|
796
|
-
let list = suffixIndex.get(filename);
|
|
797
|
-
if (!list) {
|
|
798
|
-
list = [];
|
|
799
|
-
suffixIndex.set(filename, list);
|
|
800
|
-
}
|
|
801
|
-
list.push(allFileList[i]);
|
|
802
|
-
}
|
|
803
351
|
for (const [filePath, fileImports] of importsByFile) {
|
|
804
352
|
filesProcessed++;
|
|
805
353
|
if (filesProcessed % 100 === 0) {
|
|
806
354
|
onProgress?.(filesProcessed, totalFiles);
|
|
807
355
|
await yieldToEventLoop();
|
|
808
356
|
}
|
|
809
|
-
for (const
|
|
357
|
+
for (const imp of fileImports) {
|
|
810
358
|
totalImportsFound++;
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
if (resolveCache.has(cacheKey)) {
|
|
814
|
-
const cached = resolveCache.get(cacheKey);
|
|
815
|
-
if (cached)
|
|
816
|
-
addImportEdge(filePath, cached);
|
|
817
|
-
continue;
|
|
818
|
-
}
|
|
819
|
-
// JVM languages (Java + Kotlin): handle wildcards and member imports
|
|
820
|
-
if (language === SupportedLanguages.Java || language === SupportedLanguages.Kotlin) {
|
|
821
|
-
const exts = language === SupportedLanguages.Java ? ['.java'] : KOTLIN_EXTENSIONS;
|
|
822
|
-
if (rawImportPath.endsWith('.*')) {
|
|
823
|
-
const matchedFiles = resolveJvmWildcard(rawImportPath, normalizedFileList, allFileList, exts, index);
|
|
824
|
-
// Kotlin can import Java files in mixed codebases — try .java as fallback
|
|
825
|
-
if (matchedFiles.length === 0 && language === SupportedLanguages.Kotlin) {
|
|
826
|
-
const javaMatches = resolveJvmWildcard(rawImportPath, normalizedFileList, allFileList, ['.java'], index);
|
|
827
|
-
for (const matchedFile of javaMatches) {
|
|
828
|
-
addImportEdge(filePath, matchedFile);
|
|
829
|
-
}
|
|
830
|
-
if (javaMatches.length > 0)
|
|
831
|
-
continue;
|
|
832
|
-
}
|
|
833
|
-
for (const matchedFile of matchedFiles) {
|
|
834
|
-
addImportEdge(filePath, matchedFile);
|
|
835
|
-
}
|
|
836
|
-
continue;
|
|
837
|
-
}
|
|
838
|
-
let memberResolved = resolveJvmMemberImport(rawImportPath, normalizedFileList, allFileList, exts, index);
|
|
839
|
-
// Kotlin can import Java files in mixed codebases — try .java as fallback
|
|
840
|
-
if (!memberResolved && language === SupportedLanguages.Kotlin) {
|
|
841
|
-
memberResolved = resolveJvmMemberImport(rawImportPath, normalizedFileList, allFileList, ['.java'], index);
|
|
842
|
-
}
|
|
843
|
-
if (memberResolved) {
|
|
844
|
-
resolveCache.set(cacheKey, memberResolved);
|
|
845
|
-
addImportEdge(filePath, memberResolved);
|
|
846
|
-
continue;
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
// Go: handle package-level imports
|
|
850
|
-
if (language === SupportedLanguages.Go && goModule && rawImportPath.startsWith(goModule.modulePath)) {
|
|
851
|
-
const pkgFiles = resolveGoPackage(rawImportPath, goModule, normalizedFileList, allFileList);
|
|
852
|
-
if (pkgFiles.length > 0) {
|
|
853
|
-
for (const pkgFile of pkgFiles) {
|
|
854
|
-
addImportEdge(filePath, pkgFile);
|
|
855
|
-
}
|
|
856
|
-
continue;
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
// PHP: handle namespace-based imports (use statements)
|
|
860
|
-
if (language === SupportedLanguages.PHP) {
|
|
861
|
-
const resolved = resolvePhpImport(rawImportPath, composerConfig, allFilePaths, normalizedFileList, allFileList, index);
|
|
862
|
-
if (resolved) {
|
|
863
|
-
resolveCache.set(cacheKey, resolved);
|
|
864
|
-
addImportEdge(filePath, resolved);
|
|
865
|
-
}
|
|
866
|
-
continue;
|
|
867
|
-
}
|
|
868
|
-
// Swift: handle module imports
|
|
869
|
-
if (language === SupportedLanguages.Swift && swiftPackageConfig) {
|
|
870
|
-
const targetDir = swiftPackageConfig.targets.get(rawImportPath);
|
|
871
|
-
if (targetDir) {
|
|
872
|
-
const dirPrefix = targetDir + '/';
|
|
873
|
-
for (const fp of allFileList) {
|
|
874
|
-
if (fp.startsWith(dirPrefix) && fp.endsWith('.swift')) {
|
|
875
|
-
addImportEdge(filePath, fp);
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
continue;
|
|
880
|
-
}
|
|
881
|
-
// Standard resolution (has its own internal cache)
|
|
882
|
-
const resolvedPath = resolveImportPath(filePath, rawImportPath, allFilePaths, allFileList, normalizedFileList, resolveCache, language, tsconfigPaths, index);
|
|
883
|
-
if (resolvedPath) {
|
|
884
|
-
addImportEdge(filePath, resolvedPath);
|
|
885
|
-
}
|
|
359
|
+
const result = resolveLanguageImport(filePath, imp.rawImportPath, imp.language, configs, resolveCtx);
|
|
360
|
+
applyImportResult(result, filePath, importMap, packageMap, addImportEdge, addImportGraphEdge, imp.namedBindings, namedImportMap);
|
|
886
361
|
}
|
|
887
362
|
}
|
|
888
363
|
onProgress?.(totalFiles, totalFiles);
|