@kodus/kodus-graph 0.2.8 → 0.2.10

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.
Files changed (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +252 -0
  3. package/dist/analysis/blast-radius.d.ts +2 -0
  4. package/dist/analysis/blast-radius.js +55 -0
  5. package/dist/analysis/communities.d.ts +28 -0
  6. package/dist/analysis/communities.js +100 -0
  7. package/dist/analysis/context-builder.d.ts +34 -0
  8. package/dist/analysis/context-builder.js +92 -0
  9. package/dist/analysis/diff.d.ts +41 -0
  10. package/dist/analysis/diff.js +155 -0
  11. package/dist/analysis/enrich.d.ts +5 -0
  12. package/dist/analysis/enrich.js +126 -0
  13. package/dist/analysis/flows.d.ts +27 -0
  14. package/dist/analysis/flows.js +86 -0
  15. package/dist/analysis/inheritance.d.ts +3 -0
  16. package/dist/analysis/inheritance.js +31 -0
  17. package/dist/analysis/prompt-formatter.d.ts +2 -0
  18. package/dist/analysis/prompt-formatter.js +173 -0
  19. package/dist/analysis/risk-score.d.ts +4 -0
  20. package/dist/analysis/risk-score.js +51 -0
  21. package/dist/analysis/search.d.ts +11 -0
  22. package/dist/analysis/search.js +64 -0
  23. package/dist/analysis/test-gaps.d.ts +2 -0
  24. package/dist/analysis/test-gaps.js +14 -0
  25. package/dist/cli.d.ts +2 -0
  26. package/dist/cli.js +210 -0
  27. package/dist/commands/analyze.d.ts +9 -0
  28. package/dist/commands/analyze.js +116 -0
  29. package/dist/commands/communities.d.ts +8 -0
  30. package/dist/commands/communities.js +9 -0
  31. package/dist/commands/context.d.ts +12 -0
  32. package/dist/commands/context.js +130 -0
  33. package/dist/commands/diff.d.ts +9 -0
  34. package/dist/commands/diff.js +89 -0
  35. package/dist/commands/flows.d.ts +8 -0
  36. package/dist/commands/flows.js +9 -0
  37. package/dist/commands/parse.d.ts +11 -0
  38. package/dist/commands/parse.js +101 -0
  39. package/dist/commands/search.d.ts +12 -0
  40. package/dist/commands/search.js +27 -0
  41. package/dist/commands/update.d.ts +7 -0
  42. package/dist/commands/update.js +154 -0
  43. package/dist/graph/builder.d.ts +6 -0
  44. package/dist/graph/builder.js +248 -0
  45. package/dist/graph/edges.d.ts +23 -0
  46. package/dist/graph/edges.js +159 -0
  47. package/dist/graph/json-writer.d.ts +9 -0
  48. package/dist/graph/json-writer.js +38 -0
  49. package/dist/graph/loader.d.ts +13 -0
  50. package/dist/graph/loader.js +101 -0
  51. package/dist/graph/merger.d.ts +7 -0
  52. package/dist/graph/merger.js +18 -0
  53. package/dist/graph/types.d.ts +252 -0
  54. package/dist/graph/types.js +1 -0
  55. package/dist/parser/batch.d.ts +5 -0
  56. package/dist/parser/batch.js +93 -0
  57. package/dist/parser/discovery.d.ts +7 -0
  58. package/dist/parser/discovery.js +61 -0
  59. package/dist/parser/extractor.d.ts +4 -0
  60. package/dist/parser/extractor.js +33 -0
  61. package/dist/parser/extractors/generic.d.ts +8 -0
  62. package/dist/parser/extractors/generic.js +471 -0
  63. package/dist/parser/extractors/python.d.ts +8 -0
  64. package/dist/parser/extractors/python.js +133 -0
  65. package/dist/parser/extractors/ruby.d.ts +8 -0
  66. package/dist/parser/extractors/ruby.js +153 -0
  67. package/dist/parser/extractors/typescript.d.ts +10 -0
  68. package/dist/parser/extractors/typescript.js +365 -0
  69. package/dist/parser/languages.d.ts +32 -0
  70. package/dist/parser/languages.js +304 -0
  71. package/dist/resolver/call-resolver.d.ts +36 -0
  72. package/dist/resolver/call-resolver.js +178 -0
  73. package/dist/resolver/external-detector.d.ts +11 -0
  74. package/dist/resolver/external-detector.js +820 -0
  75. package/dist/resolver/fs-cache.d.ts +8 -0
  76. package/dist/resolver/fs-cache.js +36 -0
  77. package/dist/resolver/import-map.d.ts +12 -0
  78. package/dist/resolver/import-map.js +21 -0
  79. package/dist/resolver/import-resolver.d.ts +19 -0
  80. package/dist/resolver/import-resolver.js +310 -0
  81. package/dist/resolver/languages/csharp.d.ts +3 -0
  82. package/dist/resolver/languages/csharp.js +94 -0
  83. package/dist/resolver/languages/go.d.ts +3 -0
  84. package/dist/resolver/languages/go.js +197 -0
  85. package/dist/resolver/languages/java.d.ts +1 -0
  86. package/dist/resolver/languages/java.js +193 -0
  87. package/dist/resolver/languages/php.d.ts +3 -0
  88. package/dist/resolver/languages/php.js +75 -0
  89. package/dist/resolver/languages/python.d.ts +11 -0
  90. package/dist/resolver/languages/python.js +127 -0
  91. package/dist/resolver/languages/ruby.d.ts +24 -0
  92. package/dist/resolver/languages/ruby.js +110 -0
  93. package/dist/resolver/languages/rust.d.ts +1 -0
  94. package/dist/resolver/languages/rust.js +197 -0
  95. package/dist/resolver/languages/typescript.d.ts +35 -0
  96. package/dist/resolver/languages/typescript.js +416 -0
  97. package/dist/resolver/re-export-resolver.d.ts +24 -0
  98. package/dist/resolver/re-export-resolver.js +57 -0
  99. package/dist/resolver/symbol-table.d.ts +17 -0
  100. package/dist/resolver/symbol-table.js +60 -0
  101. package/dist/shared/extract-calls.d.ts +26 -0
  102. package/dist/shared/extract-calls.js +57 -0
  103. package/dist/shared/file-hash.d.ts +3 -0
  104. package/dist/shared/file-hash.js +10 -0
  105. package/dist/shared/filters.d.ts +3 -0
  106. package/dist/shared/filters.js +240 -0
  107. package/dist/shared/logger.d.ts +6 -0
  108. package/dist/shared/logger.js +17 -0
  109. package/dist/shared/qualified-name.d.ts +1 -0
  110. package/dist/shared/qualified-name.js +9 -0
  111. package/dist/shared/safe-path.d.ts +6 -0
  112. package/dist/shared/safe-path.js +29 -0
  113. package/dist/shared/schemas.d.ts +43 -0
  114. package/dist/shared/schemas.js +30 -0
  115. package/dist/shared/temp.d.ts +11 -0
  116. package/{src/shared/temp.ts → dist/shared/temp.js} +4 -5
  117. package/package.json +20 -6
  118. package/src/analysis/blast-radius.ts +0 -54
  119. package/src/analysis/communities.ts +0 -135
  120. package/src/analysis/context-builder.ts +0 -130
  121. package/src/analysis/diff.ts +0 -169
  122. package/src/analysis/enrich.ts +0 -110
  123. package/src/analysis/flows.ts +0 -112
  124. package/src/analysis/inheritance.ts +0 -34
  125. package/src/analysis/prompt-formatter.ts +0 -175
  126. package/src/analysis/risk-score.ts +0 -62
  127. package/src/analysis/search.ts +0 -76
  128. package/src/analysis/test-gaps.ts +0 -21
  129. package/src/cli.ts +0 -210
  130. package/src/commands/analyze.ts +0 -128
  131. package/src/commands/communities.ts +0 -19
  132. package/src/commands/context.ts +0 -182
  133. package/src/commands/diff.ts +0 -96
  134. package/src/commands/flows.ts +0 -19
  135. package/src/commands/parse.ts +0 -124
  136. package/src/commands/search.ts +0 -41
  137. package/src/commands/update.ts +0 -166
  138. package/src/graph/builder.ts +0 -209
  139. package/src/graph/edges.ts +0 -101
  140. package/src/graph/json-writer.ts +0 -43
  141. package/src/graph/loader.ts +0 -113
  142. package/src/graph/merger.ts +0 -25
  143. package/src/graph/types.ts +0 -283
  144. package/src/parser/batch.ts +0 -82
  145. package/src/parser/discovery.ts +0 -75
  146. package/src/parser/extractor.ts +0 -37
  147. package/src/parser/extractors/generic.ts +0 -132
  148. package/src/parser/extractors/python.ts +0 -133
  149. package/src/parser/extractors/ruby.ts +0 -147
  150. package/src/parser/extractors/typescript.ts +0 -350
  151. package/src/parser/languages.ts +0 -122
  152. package/src/resolver/call-resolver.ts +0 -244
  153. package/src/resolver/import-map.ts +0 -27
  154. package/src/resolver/import-resolver.ts +0 -72
  155. package/src/resolver/languages/csharp.ts +0 -7
  156. package/src/resolver/languages/go.ts +0 -7
  157. package/src/resolver/languages/java.ts +0 -7
  158. package/src/resolver/languages/php.ts +0 -7
  159. package/src/resolver/languages/python.ts +0 -35
  160. package/src/resolver/languages/ruby.ts +0 -21
  161. package/src/resolver/languages/rust.ts +0 -7
  162. package/src/resolver/languages/typescript.ts +0 -168
  163. package/src/resolver/re-export-resolver.ts +0 -66
  164. package/src/resolver/symbol-table.ts +0 -67
  165. package/src/shared/extract-calls.ts +0 -75
  166. package/src/shared/file-hash.ts +0 -12
  167. package/src/shared/filters.ts +0 -243
  168. package/src/shared/logger.ts +0 -17
  169. package/src/shared/qualified-name.ts +0 -5
  170. package/src/shared/safe-path.ts +0 -31
  171. package/src/shared/schemas.ts +0 -32
@@ -1,122 +0,0 @@
1
- import csharp from '@ast-grep/lang-csharp';
2
- import go from '@ast-grep/lang-go';
3
- import java from '@ast-grep/lang-java';
4
- import php from '@ast-grep/lang-php';
5
- import python from '@ast-grep/lang-python';
6
- import ruby from '@ast-grep/lang-ruby';
7
- import rust from '@ast-grep/lang-rust';
8
- import { Lang, registerDynamicLanguage } from '@ast-grep/napi';
9
-
10
- // Register dynamic languages at import time (side effect).
11
- // This must happen before parseAsync can parse these languages.
12
- registerDynamicLanguage({ python, ruby, go, java, rust, php, csharp });
13
-
14
- // Extension -> language identifier
15
- // Built-in langs use Lang enum, dynamic langs use lowercase string
16
- const EXT_TO_LANG: Record<string, Lang | string> = {
17
- '.ts': Lang.TypeScript,
18
- '.tsx': Lang.Tsx,
19
- '.js': Lang.JavaScript,
20
- '.jsx': Lang.JavaScript,
21
- '.mjs': Lang.JavaScript,
22
- '.cjs': Lang.JavaScript,
23
- '.py': 'python',
24
- '.rb': 'ruby',
25
- '.go': 'go',
26
- '.java': 'java',
27
- '.rs': 'rust',
28
- '.cs': 'csharp',
29
- '.php': 'php',
30
- };
31
-
32
- export function getLanguage(ext: string): Lang | string | null {
33
- return EXT_TO_LANG[ext] ?? null;
34
- }
35
-
36
- export function getSupportedExtensions(): string[] {
37
- return Object.keys(EXT_TO_LANG);
38
- }
39
-
40
- export function getLanguageName(lang: Lang | string): string {
41
- if (typeof lang === 'string') return lang;
42
- if (lang === Lang.TypeScript || lang === Lang.Tsx) return 'typescript';
43
- if (lang === Lang.JavaScript) return 'javascript';
44
- return 'unknown';
45
- }
46
-
47
- export function isTypeScriptLike(lang: Lang | string): boolean {
48
- return lang === Lang.TypeScript || lang === Lang.Tsx || lang === Lang.JavaScript;
49
- }
50
-
51
- // AST node kinds per language for structural extraction
52
- export const LANG_KINDS: Record<string, Record<string, string>> = {
53
- typescript: {
54
- class: 'class_declaration',
55
- abstractClass: 'abstract_class_declaration',
56
- method: 'method_definition',
57
- function: 'function_declaration',
58
- arrowContainer: 'variable_declarator',
59
- arrowFunction: 'arrow_function',
60
- interface: 'interface_declaration',
61
- enum: 'enum_declaration',
62
- import: 'import_statement',
63
- export: 'export_statement',
64
- methodSignature: 'method_signature',
65
- },
66
- python: {
67
- class: 'class_definition',
68
- method: 'function_definition',
69
- function: 'function_definition',
70
- import: 'import_from_statement',
71
- importRegular: 'import_statement',
72
- decorator: 'decorator',
73
- },
74
- ruby: {
75
- class: 'class',
76
- method: 'method',
77
- singletonMethod: 'singleton_method',
78
- module: 'module',
79
- call: 'call',
80
- },
81
- go: {
82
- function: 'function_declaration',
83
- method: 'method_declaration',
84
- struct: 'type_declaration',
85
- interface: 'type_declaration',
86
- import: 'import_declaration',
87
- },
88
- java: {
89
- class: 'class_declaration',
90
- interface: 'interface_declaration',
91
- method: 'method_declaration',
92
- constructor: 'constructor_declaration',
93
- import: 'import_declaration',
94
- enum: 'enum_declaration',
95
- },
96
- rust: {
97
- function: 'function_item',
98
- struct: 'struct_item',
99
- impl: 'impl_item',
100
- trait: 'trait_item',
101
- enum: 'enum_item',
102
- use: 'use_declaration',
103
- },
104
- csharp: {
105
- class: 'class_declaration',
106
- interface: 'interface_declaration',
107
- method: 'method_declaration',
108
- constructor: 'constructor_declaration',
109
- using: 'using_directive',
110
- enum: 'enum_declaration',
111
- namespace: 'namespace_declaration',
112
- },
113
- php: {
114
- class: 'class_declaration',
115
- method: 'method_declaration',
116
- function: 'function_definition',
117
- namespace: 'namespace_definition',
118
- use: 'namespace_use_declaration',
119
- },
120
- };
121
-
122
- export { Lang };
@@ -1,244 +0,0 @@
1
- /**
2
- * Call resolution with 5-tier confidence cascade.
3
- *
4
- * Cascade: DI (0.90-0.95) → same-file (0.85) → import-resolved (0.70-0.90)
5
- * → unique-name (0.50) → ambiguous (0.30)
6
- *
7
- * Pure resolution logic — no file I/O, no parsing.
8
- * Raw call sites are provided by the batch parser.
9
- */
10
-
11
- import type { RawCallEdge, RawCallSite } from '../graph/types';
12
- import { NOISE } from '../shared/filters';
13
- import type { ImportMap } from './import-map';
14
- import type { SymbolTable } from './symbol-table';
15
-
16
- // ── Types ──
17
-
18
- interface ResolveResult {
19
- target: string;
20
- confidence: number;
21
- strategy: 'di' | 'same' | 'import' | 'unique' | 'ambiguous';
22
- }
23
-
24
- interface CallResolverStats {
25
- di: number;
26
- same: number;
27
- import: number;
28
- unique: number;
29
- ambiguous: number;
30
- noise: number;
31
- }
32
-
33
- interface ResolveAllResult {
34
- callEdges: RawCallEdge[];
35
- stats: CallResolverStats;
36
- }
37
-
38
- // ── Batch resolution (pure, no I/O) ──
39
-
40
- /**
41
- * Resolve all raw call sites via the 5-tier cascade.
42
- *
43
- * Accepts pre-extracted RawCallSite[] from the batch parser.
44
- * No file reads, no parseAsync — pure iteration + lookup.
45
- */
46
- export function resolveAllCalls(
47
- rawCalls: RawCallSite[],
48
- diMaps: Map<string, Map<string, string>>,
49
- symbolTable: SymbolTable,
50
- importMap: ImportMap,
51
- ): ResolveAllResult {
52
- const callEdges: RawCallEdge[] = [];
53
- const stats: CallResolverStats = { di: 0, same: 0, import: 0, unique: 0, ambiguous: 0, noise: 0 };
54
-
55
- for (const call of rawCalls) {
56
- if (NOISE.has(call.callName)) {
57
- stats.noise++;
58
- continue;
59
- }
60
-
61
- const fp = call.source;
62
- const diMap = diMaps.get(fp);
63
-
64
- // Try DI resolution first if diField is present
65
- if (call.diField) {
66
- const resolved = resolveDICall(call.diField, call.callName, fp, diMap, symbolTable);
67
- if (resolved) {
68
- callEdges.push({
69
- source: fp,
70
- target: resolved.target,
71
- callName: call.callName,
72
- line: call.line,
73
- confidence: resolved.confidence,
74
- });
75
- stats.di++;
76
- continue;
77
- }
78
- }
79
-
80
- // Class-aware resolution for self.X() and super().X()
81
- if (call.resolveInClass) {
82
- const classResolved = resolveInClass(call.callName, fp, call.resolveInClass, symbolTable);
83
- if (classResolved) {
84
- callEdges.push({
85
- source: fp,
86
- target: classResolved.target,
87
- callName: call.callName,
88
- line: call.line,
89
- confidence: classResolved.confidence,
90
- });
91
- stats[classResolved.strategy]++;
92
- continue;
93
- }
94
- }
95
-
96
- // Name-based cascade fallback
97
- const resolved = resolveByName(call.callName, fp, symbolTable, importMap);
98
- if (resolved) {
99
- callEdges.push({
100
- source: fp,
101
- target: resolved.target,
102
- callName: call.callName,
103
- line: call.line,
104
- confidence: resolved.confidence,
105
- });
106
- stats[resolved.strategy]++;
107
- }
108
- }
109
-
110
- return { callEdges, stats };
111
- }
112
-
113
- // ── Class-aware resolution (self./super.) ──
114
-
115
- function resolveInClass(
116
- callName: string,
117
- currentFile: string,
118
- className: string,
119
- symbolTable: SymbolTable,
120
- ): ResolveResult | null {
121
- // Try same-file class method first (self.method() or super().method())
122
- const inFile = symbolTable.lookupInFile(currentFile, callName, className);
123
- if (inFile) return { target: inFile, confidence: 0.9, strategy: 'same' };
124
-
125
- // Class might be in another file (imported parent class for super())
126
- const candidates = symbolTable.lookupGlobal(callName);
127
- const match = candidates.find((q) => q.includes(`::${className}.${callName}`));
128
- if (match) return { target: match, confidence: 0.85, strategy: 'import' };
129
-
130
- return null;
131
- }
132
-
133
- // ── DI resolution ──
134
-
135
- function resolveDICall(
136
- fieldName: string,
137
- methodName: string,
138
- _currentFile: string,
139
- diMap: Map<string, string> | undefined,
140
- symbolTable: SymbolTable,
141
- ): ResolveResult | null {
142
- if (!diMap?.has(fieldName)) return null;
143
-
144
- const typeName = diMap.get(fieldName)!;
145
-
146
- // Direct class match
147
- const candidates = symbolTable.lookupGlobal(typeName);
148
- if (candidates.length >= 1) {
149
- const typeFile = candidates[0].split('::')[0];
150
- return { target: `${typeFile}::${typeName}.${methodName}`, confidence: 0.95, strategy: 'di' };
151
- }
152
-
153
- // ISomething → Something heuristic for interface → implementation
154
- if (typeName.startsWith('I') && typeName[1] === typeName[1]?.toUpperCase()) {
155
- const implName = typeName.substring(1);
156
- const implCandidates = symbolTable.lookupGlobal(implName);
157
- if (implCandidates.length >= 1) {
158
- const implFile = implCandidates[0].split('::')[0];
159
- return { target: `${implFile}::${implName}.${methodName}`, confidence: 0.9, strategy: 'di' };
160
- }
161
- }
162
-
163
- return null;
164
- }
165
-
166
- // ── Name-based resolution (4-tier cascade) ──
167
-
168
- function resolveByName(
169
- callName: string,
170
- currentFile: string,
171
- symbolTable: SymbolTable,
172
- importMap: ImportMap,
173
- ): ResolveResult | null {
174
- // Strategy 1: Same file (0.85)
175
- const sameFile = symbolTable.lookupExact(currentFile, callName);
176
- if (sameFile) return { target: sameFile, confidence: 0.85, strategy: 'same' };
177
-
178
- // Strategy 2: Import-resolved (0.70-0.90)
179
- const importedFrom = importMap.lookup(currentFile, callName);
180
- if (importedFrom) {
181
- const targetSym = symbolTable.lookupExact(importedFrom, callName);
182
- if (targetSym) return { target: targetSym, confidence: 0.9, strategy: 'import' };
183
- return { target: `${importedFrom}::${callName}`, confidence: 0.7, strategy: 'import' };
184
- }
185
-
186
- // Strategy 3: Unique global name (0.50)
187
- if (symbolTable.isUnique(callName)) {
188
- const candidates = symbolTable.lookupGlobal(callName);
189
- return { target: candidates[0], confidence: 0.5, strategy: 'unique' };
190
- }
191
-
192
- // Strategy 4: Ambiguous (0.30) — pick closest candidate by directory proximity
193
- const candidates = symbolTable.lookupGlobal(callName);
194
- if (candidates.length > 1) {
195
- const best = pickClosestCandidate(candidates, currentFile);
196
- return { target: best, confidence: 0.3, strategy: 'ambiguous' };
197
- }
198
-
199
- return null;
200
- }
201
-
202
- // ── Proximity-based candidate selection ──
203
-
204
- /**
205
- * Pick the candidate whose file path is closest to the caller's file.
206
- * Counts shared leading path segments — more shared = closer.
207
- */
208
- function pickClosestCandidate(candidates: string[], callerFile: string): string {
209
- const callerParts = callerFile.split('/');
210
- let best = candidates[0];
211
- let bestScore = -1;
212
-
213
- for (const candidate of candidates) {
214
- const candidateFile = candidate.includes('::') ? candidate.split('::')[0] : candidate;
215
- const parts = candidateFile.split('/');
216
- let shared = 0;
217
- for (let i = 0; i < Math.min(callerParts.length, parts.length); i++) {
218
- if (callerParts[i] === parts[i]) shared++;
219
- else break;
220
- }
221
- if (shared > bestScore) {
222
- bestScore = shared;
223
- best = candidate;
224
- }
225
- }
226
-
227
- return best;
228
- }
229
-
230
- // ── Public wrapper for unit testing ──
231
-
232
- export function resolveCall(
233
- callName: string,
234
- currentFile: string,
235
- symbolTable: SymbolTable,
236
- importMap: ImportMap,
237
- ): { target: string; confidence: number } | null {
238
- if (NOISE.has(callName)) return null;
239
-
240
- const result = resolveByName(callName, currentFile, symbolTable, importMap);
241
- if (!result) return null;
242
-
243
- return { target: result.target, confidence: result.confidence };
244
- }
@@ -1,27 +0,0 @@
1
- /**
2
- * Import map: tracks which symbols are imported from where per file.
3
- *
4
- * For each importing file, maps symbol names to the resolved file path
5
- * they were imported from. Used by the call resolver to connect
6
- * function calls to their definitions across files.
7
- */
8
-
9
- export interface ImportMap {
10
- add(file: string, name: string, targetFile: string): void;
11
- lookup(file: string, name: string): string | null;
12
- }
13
-
14
- export function createImportMap(): ImportMap {
15
- const map = new Map<string, Map<string, string>>();
16
-
17
- return {
18
- add(file, name, targetFile) {
19
- if (!map.has(file)) map.set(file, new Map());
20
- map.get(file)!.set(name, targetFile);
21
- },
22
-
23
- lookup(file, name) {
24
- return map.get(file)?.get(name) ?? null;
25
- },
26
- };
27
- }
@@ -1,72 +0,0 @@
1
- /**
2
- * Import resolver dispatcher.
3
- *
4
- * Routes import resolution to language-specific resolvers and
5
- * falls back to tsconfig aliases for TypeScript/JavaScript.
6
- */
7
-
8
- import { log } from '../shared/logger';
9
- import { ensureWithinRoot } from '../shared/safe-path';
10
- import { resolve as resolveCsImport } from './languages/csharp';
11
- import { resolve as resolveGoImport } from './languages/go';
12
- import { resolve as resolveJavaImport } from './languages/java';
13
- import { resolve as resolvePhpImport } from './languages/php';
14
- import { resolve as resolvePyImport } from './languages/python';
15
- import { resolve as resolveRbImport } from './languages/ruby';
16
- import { resolve as resolveRustImport } from './languages/rust';
17
- import { loadTsconfigAliases, resolve as resolveTsImport, resolveWithAliases } from './languages/typescript';
18
-
19
- const RESOLVERS: Record<string, (from: string, mod: string, root: string) => string | null> = {
20
- ts: resolveTsImport,
21
- javascript: resolveTsImport,
22
- typescript: resolveTsImport,
23
- python: resolvePyImport,
24
- ruby: resolveRbImport,
25
- go: resolveGoImport,
26
- java: resolveJavaImport,
27
- rust: resolveRustImport,
28
- csharp: resolveCsImport,
29
- php: resolvePhpImport,
30
- };
31
-
32
- /**
33
- * Resolve an import from one file to another.
34
- *
35
- * @param fromAbsFile - Absolute path of the importing file
36
- * @param modulePath - The import specifier (e.g., './auth', 'express', '@/lib/db')
37
- * @param lang - Language key (ts, javascript, typescript, python, ruby, etc.)
38
- * @param repoRoot - Absolute path to the repository root
39
- * @param tsconfigAliases - Optional pre-loaded tsconfig aliases for TS/JS
40
- * @returns Absolute path to the resolved file, or null if unresolvable
41
- */
42
- export function resolveImport(
43
- fromAbsFile: string,
44
- modulePath: string,
45
- lang: string,
46
- repoRoot: string,
47
- tsconfigAliases?: Map<string, string[]>,
48
- ): string | null {
49
- const resolver = RESOLVERS[lang];
50
- if (!resolver) return null;
51
-
52
- let result = resolver(fromAbsFile, modulePath, repoRoot);
53
-
54
- // Fallback: tsconfig aliases for TS/JS
55
- if (!result && (lang === 'ts' || lang === 'javascript' || lang === 'typescript') && tsconfigAliases?.size) {
56
- result = resolveWithAliases(modulePath, tsconfigAliases, repoRoot);
57
- }
58
-
59
- // Validate resolved path is within repo root
60
- if (result) {
61
- try {
62
- ensureWithinRoot(result, repoRoot);
63
- } catch {
64
- log.warn('Import resolves outside repository root', { from: fromAbsFile, module: modulePath, resolved: result });
65
- return null;
66
- }
67
- }
68
-
69
- return result;
70
- }
71
-
72
- export { loadTsconfigAliases };
@@ -1,7 +0,0 @@
1
- /**
2
- * C# import resolver (stub).
3
- */
4
-
5
- export function resolve(_fromAbsFile: string, _modulePath: string, _repoRoot: string): string | null {
6
- return null;
7
- }
@@ -1,7 +0,0 @@
1
- /**
2
- * Go import resolver (stub).
3
- */
4
-
5
- export function resolve(_fromAbsFile: string, _modulePath: string, _repoRoot: string): string | null {
6
- return null;
7
- }
@@ -1,7 +0,0 @@
1
- /**
2
- * Java import resolver (stub).
3
- */
4
-
5
- export function resolve(_fromAbsFile: string, _modulePath: string, _repoRoot: string): string | null {
6
- return null;
7
- }
@@ -1,7 +0,0 @@
1
- /**
2
- * PHP import resolver (stub).
3
- */
4
-
5
- export function resolve(_fromAbsFile: string, _modulePath: string, _repoRoot: string): string | null {
6
- return null;
7
- }
@@ -1,35 +0,0 @@
1
- /**
2
- * Python import resolver.
3
- *
4
- * Handles dotted module paths (e.g., "from x.y import z").
5
- * Walks up directories to find packages.
6
- */
7
-
8
- import { existsSync } from 'fs';
9
- import { dirname, join, resolve as resolvePath } from 'path';
10
-
11
- /**
12
- * Resolve a Python dotted import to a file path.
13
- * Walks up from the importing file's directory to find the module.
14
- */
15
- export function resolve(fromAbsFile: string, modulePath: string, _repoRoot: string): string | null {
16
- if (!modulePath || modulePath.startsWith('.')) {
17
- // Relative import -- not handled yet
18
- return null;
19
- }
20
-
21
- const parts = modulePath.replace(/\./g, '/');
22
- let current = dirname(fromAbsFile);
23
-
24
- for (let i = 0; i < 10; i++) {
25
- for (const candidate of [`${parts}.py`, `${parts}/__init__.py`]) {
26
- const full = join(current, candidate);
27
- if (existsSync(full)) return resolvePath(full);
28
- }
29
- const parent = dirname(current);
30
- if (parent === current) break;
31
- current = parent;
32
- }
33
-
34
- return null;
35
- }
@@ -1,21 +0,0 @@
1
- /**
2
- * Ruby import resolver.
3
- *
4
- * Handles require_relative paths.
5
- */
6
-
7
- import { existsSync } from 'fs';
8
- import { dirname, join, resolve as resolvePath } from 'path';
9
-
10
- /**
11
- * Resolve a Ruby require/require_relative to a file path.
12
- */
13
- export function resolve(fromAbsFile: string, modulePath: string, _repoRoot: string): string | null {
14
- if (!modulePath) return null;
15
-
16
- const base = join(dirname(fromAbsFile), modulePath);
17
- if (existsSync(`${base}.rb`)) return resolvePath(`${base}.rb`);
18
- if (existsSync(base)) return resolvePath(base);
19
-
20
- return null;
21
- }
@@ -1,7 +0,0 @@
1
- /**
2
- * Rust import resolver (stub).
3
- */
4
-
5
- export function resolve(_fromAbsFile: string, _modulePath: string, _repoRoot: string): string | null {
6
- return null;
7
- }