@kodus/kodus-graph 0.2.8 → 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.
- package/LICENSE +21 -0
- package/README.md +252 -0
- package/dist/analysis/blast-radius.d.ts +2 -0
- package/dist/analysis/blast-radius.js +57 -0
- package/dist/analysis/communities.d.ts +28 -0
- package/dist/analysis/communities.js +100 -0
- package/dist/analysis/context-builder.d.ts +34 -0
- package/dist/analysis/context-builder.js +83 -0
- package/dist/analysis/diff.d.ts +35 -0
- package/dist/analysis/diff.js +140 -0
- package/dist/analysis/enrich.d.ts +5 -0
- package/dist/analysis/enrich.js +98 -0
- package/dist/analysis/flows.d.ts +27 -0
- package/dist/analysis/flows.js +86 -0
- package/dist/analysis/inheritance.d.ts +3 -0
- package/dist/analysis/inheritance.js +31 -0
- package/dist/analysis/prompt-formatter.d.ts +2 -0
- package/dist/analysis/prompt-formatter.js +166 -0
- package/dist/analysis/risk-score.d.ts +4 -0
- package/dist/analysis/risk-score.js +51 -0
- package/dist/analysis/search.d.ts +11 -0
- package/dist/analysis/search.js +64 -0
- package/dist/analysis/test-gaps.d.ts +2 -0
- package/dist/analysis/test-gaps.js +14 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +208 -0
- package/dist/commands/analyze.d.ts +9 -0
- package/dist/commands/analyze.js +114 -0
- package/dist/commands/communities.d.ts +8 -0
- package/dist/commands/communities.js +9 -0
- package/dist/commands/context.d.ts +12 -0
- package/dist/commands/context.js +130 -0
- package/dist/commands/diff.d.ts +9 -0
- package/dist/commands/diff.js +89 -0
- package/dist/commands/flows.d.ts +8 -0
- package/dist/commands/flows.js +9 -0
- package/dist/commands/parse.d.ts +10 -0
- package/dist/commands/parse.js +101 -0
- package/dist/commands/search.d.ts +12 -0
- package/dist/commands/search.js +27 -0
- package/dist/commands/update.d.ts +7 -0
- package/dist/commands/update.js +154 -0
- package/dist/graph/builder.d.ts +2 -0
- package/dist/graph/builder.js +216 -0
- package/dist/graph/edges.d.ts +19 -0
- package/dist/graph/edges.js +105 -0
- package/dist/graph/json-writer.d.ts +9 -0
- package/dist/graph/json-writer.js +38 -0
- package/dist/graph/loader.d.ts +13 -0
- package/dist/graph/loader.js +101 -0
- package/dist/graph/merger.d.ts +7 -0
- package/dist/graph/merger.js +18 -0
- package/dist/graph/types.d.ts +249 -0
- package/dist/graph/types.js +1 -0
- package/dist/parser/batch.d.ts +4 -0
- package/dist/parser/batch.js +78 -0
- package/dist/parser/discovery.d.ts +7 -0
- package/dist/parser/discovery.js +61 -0
- package/dist/parser/extractor.d.ts +4 -0
- package/dist/parser/extractor.js +33 -0
- package/dist/parser/extractors/generic.d.ts +8 -0
- package/dist/parser/extractors/generic.js +471 -0
- package/dist/parser/extractors/python.d.ts +8 -0
- package/dist/parser/extractors/python.js +133 -0
- package/dist/parser/extractors/ruby.d.ts +8 -0
- package/dist/parser/extractors/ruby.js +153 -0
- package/dist/parser/extractors/typescript.d.ts +10 -0
- package/dist/parser/extractors/typescript.js +365 -0
- package/dist/parser/languages.d.ts +32 -0
- package/dist/parser/languages.js +303 -0
- package/dist/resolver/call-resolver.d.ts +36 -0
- package/dist/resolver/call-resolver.js +178 -0
- package/dist/resolver/import-map.d.ts +12 -0
- package/dist/resolver/import-map.js +21 -0
- package/dist/resolver/import-resolver.d.ts +19 -0
- package/dist/resolver/import-resolver.js +212 -0
- package/dist/resolver/languages/csharp.d.ts +1 -0
- package/dist/resolver/languages/csharp.js +31 -0
- package/dist/resolver/languages/go.d.ts +3 -0
- package/dist/resolver/languages/go.js +196 -0
- package/dist/resolver/languages/java.d.ts +1 -0
- package/dist/resolver/languages/java.js +108 -0
- package/dist/resolver/languages/php.d.ts +3 -0
- package/dist/resolver/languages/php.js +54 -0
- package/dist/resolver/languages/python.d.ts +11 -0
- package/dist/resolver/languages/python.js +51 -0
- package/dist/resolver/languages/ruby.d.ts +9 -0
- package/dist/resolver/languages/ruby.js +59 -0
- package/dist/resolver/languages/rust.d.ts +1 -0
- package/dist/resolver/languages/rust.js +196 -0
- package/dist/resolver/languages/typescript.d.ts +27 -0
- package/dist/resolver/languages/typescript.js +240 -0
- package/dist/resolver/re-export-resolver.d.ts +24 -0
- package/dist/resolver/re-export-resolver.js +57 -0
- package/dist/resolver/symbol-table.d.ts +17 -0
- package/dist/resolver/symbol-table.js +60 -0
- package/dist/shared/extract-calls.d.ts +26 -0
- package/dist/shared/extract-calls.js +57 -0
- package/dist/shared/file-hash.d.ts +3 -0
- package/dist/shared/file-hash.js +10 -0
- package/dist/shared/filters.d.ts +3 -0
- package/dist/shared/filters.js +240 -0
- package/dist/shared/logger.d.ts +6 -0
- package/dist/shared/logger.js +17 -0
- package/dist/shared/qualified-name.d.ts +1 -0
- package/dist/shared/qualified-name.js +9 -0
- package/dist/shared/safe-path.d.ts +6 -0
- package/dist/shared/safe-path.js +29 -0
- package/dist/shared/schemas.d.ts +43 -0
- package/dist/shared/schemas.js +30 -0
- package/dist/shared/temp.d.ts +11 -0
- package/{src/shared/temp.ts → dist/shared/temp.js} +4 -5
- package/package.json +20 -6
- package/src/analysis/blast-radius.ts +0 -54
- package/src/analysis/communities.ts +0 -135
- package/src/analysis/context-builder.ts +0 -130
- package/src/analysis/diff.ts +0 -169
- package/src/analysis/enrich.ts +0 -110
- package/src/analysis/flows.ts +0 -112
- package/src/analysis/inheritance.ts +0 -34
- package/src/analysis/prompt-formatter.ts +0 -175
- package/src/analysis/risk-score.ts +0 -62
- package/src/analysis/search.ts +0 -76
- package/src/analysis/test-gaps.ts +0 -21
- package/src/cli.ts +0 -210
- package/src/commands/analyze.ts +0 -128
- package/src/commands/communities.ts +0 -19
- package/src/commands/context.ts +0 -182
- package/src/commands/diff.ts +0 -96
- package/src/commands/flows.ts +0 -19
- package/src/commands/parse.ts +0 -124
- package/src/commands/search.ts +0 -41
- package/src/commands/update.ts +0 -166
- package/src/graph/builder.ts +0 -209
- package/src/graph/edges.ts +0 -101
- package/src/graph/json-writer.ts +0 -43
- package/src/graph/loader.ts +0 -113
- package/src/graph/merger.ts +0 -25
- package/src/graph/types.ts +0 -283
- package/src/parser/batch.ts +0 -82
- package/src/parser/discovery.ts +0 -75
- package/src/parser/extractor.ts +0 -37
- package/src/parser/extractors/generic.ts +0 -132
- package/src/parser/extractors/python.ts +0 -133
- package/src/parser/extractors/ruby.ts +0 -147
- package/src/parser/extractors/typescript.ts +0 -350
- package/src/parser/languages.ts +0 -122
- package/src/resolver/call-resolver.ts +0 -244
- package/src/resolver/import-map.ts +0 -27
- package/src/resolver/import-resolver.ts +0 -72
- package/src/resolver/languages/csharp.ts +0 -7
- package/src/resolver/languages/go.ts +0 -7
- package/src/resolver/languages/java.ts +0 -7
- package/src/resolver/languages/php.ts +0 -7
- package/src/resolver/languages/python.ts +0 -35
- package/src/resolver/languages/ruby.ts +0 -21
- package/src/resolver/languages/rust.ts +0 -7
- package/src/resolver/languages/typescript.ts +0 -168
- package/src/resolver/re-export-resolver.ts +0 -66
- package/src/resolver/symbol-table.ts +0 -67
- package/src/shared/extract-calls.ts +0 -75
- package/src/shared/file-hash.ts +0 -12
- package/src/shared/filters.ts +0 -243
- package/src/shared/logger.ts +0 -17
- package/src/shared/qualified-name.ts +0 -5
- package/src/shared/safe-path.ts +0 -31
- 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 };
|