@kodus/kodus-graph 0.2.7 → 0.2.9

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 (167) 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 +57 -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 +83 -0
  9. package/dist/analysis/diff.d.ts +35 -0
  10. package/dist/analysis/diff.js +140 -0
  11. package/dist/analysis/enrich.d.ts +5 -0
  12. package/dist/analysis/enrich.js +98 -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 +166 -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 +208 -0
  27. package/dist/commands/analyze.d.ts +9 -0
  28. package/dist/commands/analyze.js +114 -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 +10 -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 +2 -0
  44. package/dist/graph/builder.js +216 -0
  45. package/dist/graph/edges.d.ts +19 -0
  46. package/dist/graph/edges.js +105 -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 +249 -0
  54. package/dist/graph/types.js +1 -0
  55. package/dist/parser/batch.d.ts +4 -0
  56. package/dist/parser/batch.js +78 -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 +303 -0
  71. package/dist/resolver/call-resolver.d.ts +36 -0
  72. package/dist/resolver/call-resolver.js +178 -0
  73. package/dist/resolver/import-map.d.ts +12 -0
  74. package/dist/resolver/import-map.js +21 -0
  75. package/dist/resolver/import-resolver.d.ts +19 -0
  76. package/dist/resolver/import-resolver.js +212 -0
  77. package/dist/resolver/languages/csharp.d.ts +1 -0
  78. package/dist/resolver/languages/csharp.js +31 -0
  79. package/dist/resolver/languages/go.d.ts +3 -0
  80. package/dist/resolver/languages/go.js +196 -0
  81. package/dist/resolver/languages/java.d.ts +1 -0
  82. package/dist/resolver/languages/java.js +108 -0
  83. package/dist/resolver/languages/php.d.ts +3 -0
  84. package/dist/resolver/languages/php.js +54 -0
  85. package/dist/resolver/languages/python.d.ts +11 -0
  86. package/dist/resolver/languages/python.js +51 -0
  87. package/dist/resolver/languages/ruby.d.ts +9 -0
  88. package/dist/resolver/languages/ruby.js +59 -0
  89. package/dist/resolver/languages/rust.d.ts +1 -0
  90. package/dist/resolver/languages/rust.js +196 -0
  91. package/dist/resolver/languages/typescript.d.ts +27 -0
  92. package/dist/resolver/languages/typescript.js +240 -0
  93. package/dist/resolver/re-export-resolver.d.ts +24 -0
  94. package/dist/resolver/re-export-resolver.js +57 -0
  95. package/dist/resolver/symbol-table.d.ts +17 -0
  96. package/dist/resolver/symbol-table.js +60 -0
  97. package/dist/shared/extract-calls.d.ts +26 -0
  98. package/dist/shared/extract-calls.js +57 -0
  99. package/dist/shared/file-hash.d.ts +3 -0
  100. package/dist/shared/file-hash.js +10 -0
  101. package/dist/shared/filters.d.ts +3 -0
  102. package/dist/shared/filters.js +240 -0
  103. package/dist/shared/logger.d.ts +6 -0
  104. package/dist/shared/logger.js +17 -0
  105. package/dist/shared/qualified-name.d.ts +1 -0
  106. package/dist/shared/qualified-name.js +9 -0
  107. package/dist/shared/safe-path.d.ts +6 -0
  108. package/dist/shared/safe-path.js +29 -0
  109. package/dist/shared/schemas.d.ts +43 -0
  110. package/dist/shared/schemas.js +30 -0
  111. package/dist/shared/temp.d.ts +11 -0
  112. package/{src/shared/temp.ts → dist/shared/temp.js} +4 -5
  113. package/package.json +20 -6
  114. package/src/analysis/blast-radius.ts +0 -54
  115. package/src/analysis/communities.ts +0 -135
  116. package/src/analysis/context-builder.ts +0 -130
  117. package/src/analysis/diff.ts +0 -131
  118. package/src/analysis/enrich.ts +0 -110
  119. package/src/analysis/flows.ts +0 -112
  120. package/src/analysis/inheritance.ts +0 -34
  121. package/src/analysis/prompt-formatter.ts +0 -175
  122. package/src/analysis/risk-score.ts +0 -62
  123. package/src/analysis/search.ts +0 -76
  124. package/src/analysis/test-gaps.ts +0 -21
  125. package/src/cli.ts +0 -207
  126. package/src/commands/analyze.ts +0 -128
  127. package/src/commands/communities.ts +0 -19
  128. package/src/commands/context.ts +0 -139
  129. package/src/commands/diff.ts +0 -96
  130. package/src/commands/flows.ts +0 -19
  131. package/src/commands/parse.ts +0 -124
  132. package/src/commands/search.ts +0 -41
  133. package/src/commands/update.ts +0 -166
  134. package/src/graph/builder.ts +0 -209
  135. package/src/graph/edges.ts +0 -101
  136. package/src/graph/json-writer.ts +0 -43
  137. package/src/graph/loader.ts +0 -113
  138. package/src/graph/merger.ts +0 -25
  139. package/src/graph/types.ts +0 -283
  140. package/src/parser/batch.ts +0 -82
  141. package/src/parser/discovery.ts +0 -75
  142. package/src/parser/extractor.ts +0 -37
  143. package/src/parser/extractors/generic.ts +0 -132
  144. package/src/parser/extractors/python.ts +0 -133
  145. package/src/parser/extractors/ruby.ts +0 -147
  146. package/src/parser/extractors/typescript.ts +0 -350
  147. package/src/parser/languages.ts +0 -122
  148. package/src/resolver/call-resolver.ts +0 -244
  149. package/src/resolver/import-map.ts +0 -27
  150. package/src/resolver/import-resolver.ts +0 -72
  151. package/src/resolver/languages/csharp.ts +0 -7
  152. package/src/resolver/languages/go.ts +0 -7
  153. package/src/resolver/languages/java.ts +0 -7
  154. package/src/resolver/languages/php.ts +0 -7
  155. package/src/resolver/languages/python.ts +0 -35
  156. package/src/resolver/languages/ruby.ts +0 -21
  157. package/src/resolver/languages/rust.ts +0 -7
  158. package/src/resolver/languages/typescript.ts +0 -168
  159. package/src/resolver/re-export-resolver.ts +0 -66
  160. package/src/resolver/symbol-table.ts +0 -67
  161. package/src/shared/extract-calls.ts +0 -75
  162. package/src/shared/file-hash.ts +0 -12
  163. package/src/shared/filters.ts +0 -243
  164. package/src/shared/logger.ts +0 -14
  165. package/src/shared/qualified-name.ts +0 -5
  166. package/src/shared/safe-path.ts +0 -31
  167. package/src/shared/schemas.ts +0 -32
@@ -0,0 +1,32 @@
1
+ import type { SgNode } from '@ast-grep/napi';
2
+ import { Lang } from '@ast-grep/napi';
3
+ export type HeritageFinder = (node: SgNode) => string | string[] | undefined;
4
+ export interface TestConfig {
5
+ filePatterns?: RegExp[];
6
+ funcPatterns?: RegExp[];
7
+ /** When both filePatterns and funcPatterns are set: 'and' requires both, 'or' (default) requires either */
8
+ matchMode?: 'and' | 'or';
9
+ annotationKind?: string;
10
+ annotationNames?: string[];
11
+ }
12
+ export interface LangConfig {
13
+ class?: string[];
14
+ function?: string[];
15
+ method?: string[];
16
+ constructorKinds?: string[];
17
+ interface?: string[];
18
+ enum?: string[];
19
+ import?: string[];
20
+ heritage?: {
21
+ extends?: HeritageFinder;
22
+ implements?: HeritageFinder;
23
+ };
24
+ tests?: TestConfig;
25
+ }
26
+ export declare function getLanguage(ext: string): Lang | string | null;
27
+ export declare function getSupportedExtensions(): string[];
28
+ export declare function getLanguageName(lang: Lang | string): string;
29
+ export declare function isTypeScriptLike(lang: Lang | string): boolean;
30
+ export declare const LANG_CONFIGS: Record<string, LangConfig>;
31
+ export declare const LANG_KINDS: Record<string, Record<string, string>>;
32
+ export { Lang };
@@ -0,0 +1,303 @@
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
+ // Register dynamic languages at import time (side effect).
10
+ // This must happen before parseAsync can parse these languages.
11
+ registerDynamicLanguage({ python, ruby, go, java, rust, php, csharp });
12
+ // ---------------------------------------------------------------------------
13
+ // Extension -> language identifier
14
+ // Built-in langs use Lang enum, dynamic langs use lowercase string
15
+ // ---------------------------------------------------------------------------
16
+ const EXT_TO_LANG = {
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
+ export function getLanguage(ext) {
32
+ return EXT_TO_LANG[ext] ?? null;
33
+ }
34
+ export function getSupportedExtensions() {
35
+ return Object.keys(EXT_TO_LANG);
36
+ }
37
+ export function getLanguageName(lang) {
38
+ if (typeof lang === 'string') {
39
+ return lang;
40
+ }
41
+ if (lang === Lang.TypeScript || lang === Lang.Tsx) {
42
+ return 'typescript';
43
+ }
44
+ if (lang === Lang.JavaScript) {
45
+ return 'javascript';
46
+ }
47
+ return 'unknown';
48
+ }
49
+ export function isTypeScriptLike(lang) {
50
+ return lang === Lang.TypeScript || lang === Lang.Tsx || lang === Lang.JavaScript;
51
+ }
52
+ // ---------------------------------------------------------------------------
53
+ // Per-language LangConfig definitions
54
+ // ---------------------------------------------------------------------------
55
+ const typescriptConfig = {
56
+ class: ['class_declaration', 'abstract_class_declaration'],
57
+ method: ['method_definition'],
58
+ function: ['function_declaration'],
59
+ interface: ['interface_declaration'],
60
+ enum: ['enum_declaration'],
61
+ import: ['import_statement'],
62
+ };
63
+ const pythonConfig = {
64
+ class: ['class_definition'],
65
+ method: ['function_definition'],
66
+ function: ['function_definition'],
67
+ import: ['import_from_statement', 'import_statement'],
68
+ tests: {
69
+ funcPatterns: [/^test_/],
70
+ filePatterns: [/test_.*\.py$/, /_test\.py$/],
71
+ },
72
+ };
73
+ const rubyConfig = {
74
+ class: ['class'],
75
+ method: ['method', 'singleton_method'],
76
+ import: [],
77
+ tests: {
78
+ filePatterns: [/_spec\.rb$/, /spec_.*\.rb$/],
79
+ },
80
+ };
81
+ const goConfig = {
82
+ function: ['function_declaration'],
83
+ method: ['method_declaration'],
84
+ class: ['type_declaration'],
85
+ interface: ['type_declaration'],
86
+ import: ['import_declaration'],
87
+ tests: {
88
+ filePatterns: [/_test\.go$/],
89
+ funcPatterns: [/^Test/, /^Benchmark/],
90
+ matchMode: 'and',
91
+ },
92
+ };
93
+ const javaConfig = {
94
+ class: ['class_declaration'],
95
+ interface: ['interface_declaration'],
96
+ method: ['method_declaration'],
97
+ constructorKinds: ['constructor_declaration'],
98
+ import: ['import_declaration'],
99
+ enum: ['enum_declaration'],
100
+ heritage: {
101
+ extends: (node) => {
102
+ const superclass = node.children().find((c) => c.kind() === 'superclass');
103
+ if (!superclass) {
104
+ return undefined;
105
+ }
106
+ const typeId = superclass.children().find((c) => c.kind() === 'type_identifier');
107
+ return typeId?.text();
108
+ },
109
+ implements: (node) => {
110
+ const superInterfaces = node.children().find((c) => c.kind() === 'super_interfaces');
111
+ if (!superInterfaces) {
112
+ return [];
113
+ }
114
+ // type_identifiers may be direct children or nested in a type_list
115
+ const typeList = superInterfaces.children().find((c) => c.kind() === 'type_list');
116
+ const container = typeList || superInterfaces;
117
+ return container
118
+ .children()
119
+ .filter((c) => c.kind() === 'type_identifier')
120
+ .map((c) => c.text());
121
+ },
122
+ },
123
+ tests: {
124
+ annotationKind: 'marker_annotation',
125
+ annotationNames: ['Test', 'ParameterizedTest'],
126
+ },
127
+ };
128
+ const rustConfig = {
129
+ function: ['function_item'],
130
+ class: ['struct_item', 'impl_item'],
131
+ interface: ['trait_item'],
132
+ enum: ['enum_item'],
133
+ import: ['use_declaration'],
134
+ tests: {
135
+ annotationKind: 'attribute_item',
136
+ annotationNames: ['test'],
137
+ },
138
+ };
139
+ const csharpConfig = {
140
+ class: ['class_declaration'],
141
+ interface: ['interface_declaration'],
142
+ method: ['method_declaration'],
143
+ constructorKinds: ['constructor_declaration'],
144
+ import: ['using_directive'],
145
+ enum: ['enum_declaration'],
146
+ heritage: {
147
+ // C# base_list doesn't distinguish extends vs implements syntactically.
148
+ // Heuristic: names starting with 'I' + uppercase are interfaces (C# convention).
149
+ // First non-interface type is treated as the base class.
150
+ extends: (node) => {
151
+ const baseList = node.children().find((c) => c.kind() === 'base_list');
152
+ if (!baseList) {
153
+ return undefined;
154
+ }
155
+ const types = baseList
156
+ .children()
157
+ .filter((c) => c.kind() === 'type_identifier' || c.kind() === 'identifier')
158
+ .map((c) => c.text());
159
+ // First non-interface name is the base class
160
+ return types.find((t) => !(t.length >= 2 && t[0] === 'I' && t[1] === t[1].toUpperCase()));
161
+ },
162
+ implements: (node) => {
163
+ const baseList = node.children().find((c) => c.kind() === 'base_list');
164
+ if (!baseList) {
165
+ return undefined;
166
+ }
167
+ const types = baseList
168
+ .children()
169
+ .filter((c) => c.kind() === 'type_identifier' || c.kind() === 'identifier')
170
+ .map((c) => c.text());
171
+ // Names matching I+uppercase convention are interfaces
172
+ return types.filter((t) => t.length >= 2 && t[0] === 'I' && t[1] === t[1].toUpperCase());
173
+ },
174
+ },
175
+ tests: {
176
+ annotationKind: 'attribute',
177
+ annotationNames: ['TestMethod', 'Fact', 'Test', 'Theory'],
178
+ },
179
+ };
180
+ const phpConfig = {
181
+ class: ['class_declaration'],
182
+ interface: ['interface_declaration'],
183
+ method: ['method_declaration'],
184
+ function: ['function_definition'],
185
+ import: ['namespace_use_declaration'],
186
+ heritage: {
187
+ extends: (node) => {
188
+ const baseClause = node.children().find((c) => c.kind() === 'base_clause');
189
+ if (!baseClause) {
190
+ return undefined;
191
+ }
192
+ const name = baseClause.children().find((c) => c.kind() === 'name');
193
+ return name?.text();
194
+ },
195
+ implements: (node) => {
196
+ const ifaceClause = node.children().find((c) => c.kind() === 'class_interface_clause');
197
+ if (!ifaceClause) {
198
+ return undefined;
199
+ }
200
+ return ifaceClause
201
+ .children()
202
+ .filter((c) => c.kind() === 'name')
203
+ .map((c) => c.text());
204
+ },
205
+ },
206
+ tests: {
207
+ funcPatterns: [/^test/],
208
+ filePatterns: [/Test\.php$/],
209
+ },
210
+ };
211
+ // ---------------------------------------------------------------------------
212
+ // LANG_CONFIGS export
213
+ // ---------------------------------------------------------------------------
214
+ export const LANG_CONFIGS = {
215
+ typescript: typescriptConfig,
216
+ python: pythonConfig,
217
+ ruby: rubyConfig,
218
+ go: goConfig,
219
+ java: javaConfig,
220
+ rust: rustConfig,
221
+ csharp: csharpConfig,
222
+ php: phpConfig,
223
+ };
224
+ // ---------------------------------------------------------------------------
225
+ // Backward-compat LANG_KINDS derived from LANG_CONFIGS
226
+ // (used by the dedicated typescript.ts, python.ts, ruby.ts extractors)
227
+ // Takes the first element of each array to match the old single-string format,
228
+ // then merges in language-specific extras.
229
+ // ---------------------------------------------------------------------------
230
+ function firstOf(arr) {
231
+ return arr?.[0];
232
+ }
233
+ function derivedKinds(config) {
234
+ const result = {};
235
+ if (firstOf(config.class)) {
236
+ result.class = firstOf(config.class);
237
+ }
238
+ if (firstOf(config.function)) {
239
+ result.function = firstOf(config.function);
240
+ }
241
+ if (firstOf(config.method)) {
242
+ result.method = firstOf(config.method);
243
+ }
244
+ if (firstOf(config.constructorKinds)) {
245
+ result['constructor'] = firstOf(config.constructorKinds);
246
+ }
247
+ if (firstOf(config.interface)) {
248
+ result.interface = firstOf(config.interface);
249
+ }
250
+ if (firstOf(config.enum)) {
251
+ result.enum = firstOf(config.enum);
252
+ }
253
+ if (firstOf(config.import)) {
254
+ result.import = firstOf(config.import);
255
+ }
256
+ return result;
257
+ }
258
+ export const LANG_KINDS = {
259
+ typescript: {
260
+ ...derivedKinds(typescriptConfig),
261
+ abstractClass: 'abstract_class_declaration',
262
+ arrowContainer: 'variable_declarator',
263
+ arrowFunction: 'arrow_function',
264
+ export: 'export_statement',
265
+ methodSignature: 'method_signature',
266
+ },
267
+ python: {
268
+ ...derivedKinds(pythonConfig),
269
+ importRegular: 'import_statement',
270
+ decorator: 'decorator',
271
+ },
272
+ ruby: {
273
+ ...derivedKinds(rubyConfig),
274
+ module: 'module',
275
+ singletonMethod: 'singleton_method',
276
+ call: 'call',
277
+ },
278
+ go: {
279
+ ...derivedKinds(goConfig),
280
+ struct: 'type_declaration',
281
+ },
282
+ java: {
283
+ ...derivedKinds(javaConfig),
284
+ annotation: 'marker_annotation',
285
+ },
286
+ rust: {
287
+ ...derivedKinds(rustConfig),
288
+ impl: 'impl_item',
289
+ struct: 'struct_item',
290
+ trait: 'trait_item',
291
+ use: 'use_declaration',
292
+ },
293
+ csharp: {
294
+ ...derivedKinds(csharpConfig),
295
+ using: 'using_directive',
296
+ attribute: 'attribute',
297
+ },
298
+ php: {
299
+ ...derivedKinds(phpConfig),
300
+ namespace: 'namespace_use_declaration',
301
+ },
302
+ };
303
+ export { Lang };
@@ -0,0 +1,36 @@
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
+ import type { RawCallEdge, RawCallSite } from '../graph/types';
11
+ import type { ImportMap } from './import-map';
12
+ import type { SymbolTable } from './symbol-table';
13
+ interface CallResolverStats {
14
+ di: number;
15
+ same: number;
16
+ import: number;
17
+ unique: number;
18
+ ambiguous: number;
19
+ noise: number;
20
+ }
21
+ interface ResolveAllResult {
22
+ callEdges: RawCallEdge[];
23
+ stats: CallResolverStats;
24
+ }
25
+ /**
26
+ * Resolve all raw call sites via the 5-tier cascade.
27
+ *
28
+ * Accepts pre-extracted RawCallSite[] from the batch parser.
29
+ * No file reads, no parseAsync — pure iteration + lookup.
30
+ */
31
+ export declare function resolveAllCalls(rawCalls: RawCallSite[], diMaps: Map<string, Map<string, string>>, symbolTable: SymbolTable, importMap: ImportMap): ResolveAllResult;
32
+ export declare function resolveCall(callName: string, currentFile: string, symbolTable: SymbolTable, importMap: ImportMap): {
33
+ target: string;
34
+ confidence: number;
35
+ } | null;
36
+ export {};
@@ -0,0 +1,178 @@
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
+ import { NOISE } from '../shared/filters';
11
+ // ── Batch resolution (pure, no I/O) ──
12
+ /**
13
+ * Resolve all raw call sites via the 5-tier cascade.
14
+ *
15
+ * Accepts pre-extracted RawCallSite[] from the batch parser.
16
+ * No file reads, no parseAsync — pure iteration + lookup.
17
+ */
18
+ export function resolveAllCalls(rawCalls, diMaps, symbolTable, importMap) {
19
+ const callEdges = [];
20
+ const stats = { di: 0, same: 0, import: 0, unique: 0, ambiguous: 0, noise: 0 };
21
+ for (const call of rawCalls) {
22
+ if (NOISE.has(call.callName)) {
23
+ stats.noise++;
24
+ continue;
25
+ }
26
+ const fp = call.source;
27
+ const diMap = diMaps.get(fp);
28
+ // Try DI resolution first if diField is present
29
+ if (call.diField) {
30
+ const resolved = resolveDICall(call.diField, call.callName, fp, diMap, symbolTable);
31
+ if (resolved) {
32
+ callEdges.push({
33
+ source: fp,
34
+ target: resolved.target,
35
+ callName: call.callName,
36
+ line: call.line,
37
+ confidence: resolved.confidence,
38
+ });
39
+ stats.di++;
40
+ continue;
41
+ }
42
+ }
43
+ // Class-aware resolution for self.X() and super().X()
44
+ if (call.resolveInClass) {
45
+ const classResolved = resolveInClass(call.callName, fp, call.resolveInClass, symbolTable);
46
+ if (classResolved) {
47
+ callEdges.push({
48
+ source: fp,
49
+ target: classResolved.target,
50
+ callName: call.callName,
51
+ line: call.line,
52
+ confidence: classResolved.confidence,
53
+ });
54
+ stats[classResolved.strategy]++;
55
+ continue;
56
+ }
57
+ }
58
+ // Name-based cascade fallback
59
+ const resolved = resolveByName(call.callName, fp, symbolTable, importMap);
60
+ if (resolved) {
61
+ callEdges.push({
62
+ source: fp,
63
+ target: resolved.target,
64
+ callName: call.callName,
65
+ line: call.line,
66
+ confidence: resolved.confidence,
67
+ });
68
+ stats[resolved.strategy]++;
69
+ }
70
+ }
71
+ return { callEdges, stats };
72
+ }
73
+ // ── Class-aware resolution (self./super.) ──
74
+ function resolveInClass(callName, currentFile, className, symbolTable) {
75
+ // Try same-file class method first (self.method() or super().method())
76
+ const inFile = symbolTable.lookupInFile(currentFile, callName, className);
77
+ if (inFile) {
78
+ return { target: inFile, confidence: 0.9, strategy: 'same' };
79
+ }
80
+ // Class might be in another file (imported parent class for super())
81
+ const candidates = symbolTable.lookupGlobal(callName);
82
+ const match = candidates.find((q) => q.includes(`::${className}.${callName}`));
83
+ if (match) {
84
+ return { target: match, confidence: 0.85, strategy: 'import' };
85
+ }
86
+ return null;
87
+ }
88
+ // ── DI resolution ──
89
+ function resolveDICall(fieldName, methodName, _currentFile, diMap, symbolTable) {
90
+ if (!diMap?.has(fieldName)) {
91
+ return null;
92
+ }
93
+ const typeName = diMap.get(fieldName);
94
+ // Direct class match
95
+ const candidates = symbolTable.lookupGlobal(typeName);
96
+ if (candidates.length >= 1) {
97
+ const typeFile = candidates[0].split('::')[0];
98
+ return { target: `${typeFile}::${typeName}.${methodName}`, confidence: 0.95, strategy: 'di' };
99
+ }
100
+ // ISomething → Something heuristic for interface → implementation
101
+ if (typeName.startsWith('I') && typeName[1] === typeName[1]?.toUpperCase()) {
102
+ const implName = typeName.substring(1);
103
+ const implCandidates = symbolTable.lookupGlobal(implName);
104
+ if (implCandidates.length >= 1) {
105
+ const implFile = implCandidates[0].split('::')[0];
106
+ return { target: `${implFile}::${implName}.${methodName}`, confidence: 0.9, strategy: 'di' };
107
+ }
108
+ }
109
+ return null;
110
+ }
111
+ // ── Name-based resolution (4-tier cascade) ──
112
+ function resolveByName(callName, currentFile, symbolTable, importMap) {
113
+ // Strategy 1: Same file (0.85)
114
+ const sameFile = symbolTable.lookupExact(currentFile, callName);
115
+ if (sameFile) {
116
+ return { target: sameFile, confidence: 0.85, strategy: 'same' };
117
+ }
118
+ // Strategy 2: Import-resolved (0.70-0.90)
119
+ const importedFrom = importMap.lookup(currentFile, callName);
120
+ if (importedFrom) {
121
+ const targetSym = symbolTable.lookupExact(importedFrom, callName);
122
+ if (targetSym) {
123
+ return { target: targetSym, confidence: 0.9, strategy: 'import' };
124
+ }
125
+ return { target: `${importedFrom}::${callName}`, confidence: 0.7, strategy: 'import' };
126
+ }
127
+ // Strategy 3: Unique global name (0.50)
128
+ if (symbolTable.isUnique(callName)) {
129
+ const candidates = symbolTable.lookupGlobal(callName);
130
+ return { target: candidates[0], confidence: 0.5, strategy: 'unique' };
131
+ }
132
+ // Strategy 4: Ambiguous (0.30) — pick closest candidate by directory proximity
133
+ const candidates = symbolTable.lookupGlobal(callName);
134
+ if (candidates.length > 1) {
135
+ const best = pickClosestCandidate(candidates, currentFile);
136
+ return { target: best, confidence: 0.3, strategy: 'ambiguous' };
137
+ }
138
+ return null;
139
+ }
140
+ // ── Proximity-based candidate selection ──
141
+ /**
142
+ * Pick the candidate whose file path is closest to the caller's file.
143
+ * Counts shared leading path segments — more shared = closer.
144
+ */
145
+ function pickClosestCandidate(candidates, callerFile) {
146
+ const callerParts = callerFile.split('/');
147
+ let best = candidates[0];
148
+ let bestScore = -1;
149
+ for (const candidate of candidates) {
150
+ const candidateFile = candidate.includes('::') ? candidate.split('::')[0] : candidate;
151
+ const parts = candidateFile.split('/');
152
+ let shared = 0;
153
+ for (let i = 0; i < Math.min(callerParts.length, parts.length); i++) {
154
+ if (callerParts[i] === parts[i]) {
155
+ shared++;
156
+ }
157
+ else {
158
+ break;
159
+ }
160
+ }
161
+ if (shared > bestScore) {
162
+ bestScore = shared;
163
+ best = candidate;
164
+ }
165
+ }
166
+ return best;
167
+ }
168
+ // ── Public wrapper for unit testing ──
169
+ export function resolveCall(callName, currentFile, symbolTable, importMap) {
170
+ if (NOISE.has(callName)) {
171
+ return null;
172
+ }
173
+ const result = resolveByName(callName, currentFile, symbolTable, importMap);
174
+ if (!result) {
175
+ return null;
176
+ }
177
+ return { target: result.target, confidence: result.confidence };
178
+ }
@@ -0,0 +1,12 @@
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
+ export interface ImportMap {
9
+ add(file: string, name: string, targetFile: string): void;
10
+ lookup(file: string, name: string): string | null;
11
+ }
12
+ export declare function createImportMap(): ImportMap;
@@ -0,0 +1,21 @@
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
+ export function createImportMap() {
9
+ const map = new Map();
10
+ return {
11
+ add(file, name, targetFile) {
12
+ if (!map.has(file)) {
13
+ map.set(file, new Map());
14
+ }
15
+ map.get(file).set(name, targetFile);
16
+ },
17
+ lookup(file, name) {
18
+ return map.get(file)?.get(name) ?? null;
19
+ },
20
+ };
21
+ }
@@ -0,0 +1,19 @@
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
+ import { loadTsconfigAliases } from './languages/typescript';
8
+ /**
9
+ * Resolve an import from one file to another.
10
+ *
11
+ * @param fromAbsFile - Absolute path of the importing file
12
+ * @param modulePath - The import specifier (e.g., './auth', 'express', '@/lib/db')
13
+ * @param lang - Language key (ts, javascript, typescript, python, ruby, etc.)
14
+ * @param repoRoot - Absolute path to the repository root
15
+ * @param tsconfigAliases - Optional pre-loaded tsconfig aliases for TS/JS
16
+ * @returns Absolute path to the resolved file, or null if unresolvable
17
+ */
18
+ export declare function resolveImport(fromAbsFile: string, modulePath: string, lang: string, repoRoot: string, tsconfigAliases?: Map<string, string[]>): string | null;
19
+ export { loadTsconfigAliases };