@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,283 +0,0 @@
1
- // ── Node kinds (aligned with Postgres ast_nodes.kind) ──
2
- export type NodeKind = 'Function' | 'Method' | 'Constructor' | 'Class' | 'Interface' | 'Enum' | 'Test';
3
-
4
- // ── Edge kinds (aligned with Postgres ast_edges.kind) ──
5
- export type EdgeKind = 'CALLS' | 'IMPORTS' | 'INHERITS' | 'IMPLEMENTS' | 'TESTED_BY' | 'CONTAINS';
6
-
7
- // ── Graph node (matches ast_nodes table) ──
8
- export interface GraphNode {
9
- kind: NodeKind;
10
- name: string;
11
- qualified_name: string;
12
- file_path: string;
13
- line_start: number;
14
- line_end: number;
15
- language: string;
16
- parent_name?: string;
17
- params?: string;
18
- return_type?: string;
19
- modifiers?: string;
20
- is_test: boolean;
21
- file_hash: string;
22
- content_hash?: string;
23
- }
24
-
25
- // ── Graph edge (matches ast_edges table) ──
26
- export interface GraphEdge {
27
- kind: EdgeKind;
28
- source_qualified: string;
29
- target_qualified: string;
30
- file_path: string;
31
- line: number;
32
- confidence?: number; // 0.0-1.0, only for CALLS
33
- }
34
-
35
- // ── Full graph data ──
36
- export interface GraphData {
37
- nodes: GraphNode[];
38
- edges: GraphEdge[];
39
- }
40
-
41
- // ── Parse command output ──
42
- export interface ParseMetadata {
43
- repo_dir: string;
44
- files_parsed: number;
45
- total_nodes: number;
46
- total_edges: number;
47
- duration_ms: number;
48
- parse_errors: number;
49
- extract_errors: number;
50
- files_unchanged?: number;
51
- incremental?: boolean;
52
- }
53
-
54
- export interface ParseOutput {
55
- metadata: ParseMetadata;
56
- nodes: GraphNode[];
57
- edges: GraphEdge[];
58
- }
59
-
60
- // ── Analyze command output ──
61
- export interface BlastRadiusResult {
62
- total_functions: number;
63
- total_files: number;
64
- by_depth: Record<string, string[]>;
65
- }
66
-
67
- export interface RiskFactor {
68
- weight: number;
69
- value: number;
70
- detail: string;
71
- }
72
-
73
- export interface RiskScoreResult {
74
- level: 'LOW' | 'MEDIUM' | 'HIGH';
75
- score: number;
76
- factors: {
77
- blast_radius: RiskFactor;
78
- test_gaps: RiskFactor;
79
- complexity: RiskFactor;
80
- inheritance: RiskFactor;
81
- };
82
- }
83
-
84
- export interface TestGap {
85
- function: string;
86
- file_path: string;
87
- line_start: number;
88
- }
89
-
90
- export interface AnalysisOutput {
91
- blast_radius: BlastRadiusResult;
92
- risk_score: RiskScoreResult;
93
- test_gaps: TestGap[];
94
- }
95
-
96
- // ── Context command output ──
97
- export interface ContextMetadata {
98
- changed_functions: number;
99
- caller_count: number;
100
- callee_count: number;
101
- untested_count: number;
102
- blast_radius: { functions: number; files: number };
103
- risk_level: 'LOW' | 'MEDIUM' | 'HIGH';
104
- risk_score: number;
105
- }
106
-
107
- export interface ContextOutput {
108
- text: string;
109
- metadata: ContextMetadata;
110
- }
111
-
112
- // ── Main graph JSON (input --graph, from Postgres) ──
113
- export interface MainGraphInput {
114
- repo_id: string;
115
- sha: string;
116
- nodes: GraphNode[];
117
- edges: GraphEdge[];
118
- }
119
-
120
- // ── Context V2 types ──
121
- export interface CallerRef {
122
- qualified_name: string;
123
- name: string;
124
- file_path: string;
125
- line: number;
126
- confidence: number;
127
- }
128
-
129
- export interface CalleeRef {
130
- qualified_name: string;
131
- name: string;
132
- file_path: string;
133
- signature: string;
134
- }
135
-
136
- export interface EnrichedFunction {
137
- qualified_name: string;
138
- name: string;
139
- kind: NodeKind;
140
- signature: string;
141
- file_path: string;
142
- line_start: number;
143
- line_end: number;
144
- callers: CallerRef[];
145
- callees: CalleeRef[];
146
- has_test_coverage: boolean;
147
- diff_changes: string[];
148
- is_new: boolean;
149
- in_flows: string[];
150
- }
151
-
152
- export interface AffectedFlow {
153
- entry_point: string;
154
- type: 'test' | 'http';
155
- touches_changed: string[];
156
- depth: number;
157
- path: string[];
158
- }
159
-
160
- export interface InheritanceEntry {
161
- qualified_name: string;
162
- file_path: string;
163
- extends?: string;
164
- implements: string[];
165
- children: string[];
166
- }
167
-
168
- export interface ContextAnalysisMetadata {
169
- changed_functions_count: number;
170
- total_callers: number;
171
- total_callees: number;
172
- untested_count: number;
173
- affected_flows_count: number;
174
- duration_ms: number;
175
- min_confidence: number;
176
- }
177
-
178
- // ── Internal types used during parsing pipeline ──
179
- export interface RawFunction {
180
- name: string;
181
- file: string;
182
- line_start: number;
183
- line_end: number;
184
- params: string;
185
- returnType: string;
186
- kind: 'Function' | 'Method' | 'Constructor';
187
- className: string;
188
- qualified: string;
189
- content_hash?: string;
190
- }
191
-
192
- export interface RawClass {
193
- name: string;
194
- file: string;
195
- line_start: number;
196
- line_end: number;
197
- extends: string;
198
- implements: string;
199
- qualified: string;
200
- content_hash?: string;
201
- }
202
-
203
- export interface RawInterface {
204
- name: string;
205
- file: string;
206
- line_start: number;
207
- line_end: number;
208
- methods: string[];
209
- qualified: string;
210
- content_hash?: string;
211
- }
212
-
213
- export interface RawEnum {
214
- name: string;
215
- file: string;
216
- line_start: number;
217
- line_end: number;
218
- qualified: string;
219
- content_hash?: string;
220
- }
221
-
222
- export interface RawTest {
223
- name: string;
224
- file: string;
225
- line_start: number;
226
- line_end: number;
227
- qualified: string;
228
- content_hash?: string;
229
- }
230
-
231
- export interface RawImport {
232
- module: string;
233
- file: string;
234
- line: number;
235
- names: string[];
236
- lang: string;
237
- }
238
-
239
- export interface RawReExport {
240
- module: string;
241
- file: string;
242
- line: number;
243
- }
244
-
245
- export interface RawCallSite {
246
- source: string; // relative file path
247
- callName: string; // function or method name being called
248
- line: number; // line number of the call
249
- diField?: string; // if DI pattern (this.field.method), the field name
250
- resolveInClass?: string; // class to resolve in: current class for self.X(), parent for super().X()
251
- }
252
-
253
- export interface RawCallEdge {
254
- source: string; // file path of the caller
255
- target: string; // qualified name of the callee
256
- callName: string;
257
- line: number;
258
- confidence: number;
259
- }
260
-
261
- export interface ImportEdge {
262
- source: string; // source file
263
- target: string; // resolved target file or unresolved module
264
- resolved: boolean;
265
- line: number;
266
- }
267
-
268
- export interface RawGraph {
269
- functions: RawFunction[];
270
- classes: RawClass[];
271
- interfaces: RawInterface[];
272
- enums: RawEnum[];
273
- tests: RawTest[];
274
- imports: RawImport[];
275
- reExports: RawReExport[];
276
- rawCalls: RawCallSite[];
277
- diMaps: Map<string, Map<string, string>>; // file -> Map<fieldName, typeName>
278
- }
279
-
280
- export interface ParseBatchResult extends RawGraph {
281
- parseErrors: number;
282
- extractErrors: number;
283
- }
@@ -1,82 +0,0 @@
1
- import type { SgRoot } from '@ast-grep/napi';
2
- import { parseAsync } from '@ast-grep/napi';
3
- import { readFileSync } from 'fs';
4
- import { extname, relative } from 'path';
5
- import type { ParseBatchResult, RawCallSite, RawGraph } from '../graph/types';
6
- import { NOISE } from '../shared/filters';
7
- import { log } from '../shared/logger';
8
- import { extractCallsFromFile, extractFromFile } from './extractor';
9
- import { getLanguage } from './languages';
10
-
11
- const BATCH_SIZE = 50;
12
-
13
- export async function parseBatch(files: string[], repoRoot: string): Promise<ParseBatchResult> {
14
- const graph: RawGraph = {
15
- functions: [],
16
- classes: [],
17
- interfaces: [],
18
- enums: [],
19
- tests: [],
20
- imports: [],
21
- reExports: [],
22
- rawCalls: [],
23
- diMaps: new Map(),
24
- };
25
- const seen = new Set<string>();
26
- let parseErrors = 0;
27
- let extractErrors = 0;
28
-
29
- for (let i = 0; i < files.length; i += BATCH_SIZE) {
30
- const batch = files.slice(i, i + BATCH_SIZE);
31
-
32
- const promises = batch.map(async (filePath) => {
33
- const lang = getLanguage(extname(filePath));
34
- if (!lang) return;
35
-
36
- let source: string;
37
- try {
38
- source = readFileSync(filePath, 'utf-8');
39
- } catch (err) {
40
- log.warn('Failed to read file', { file: filePath, error: String(err) });
41
- parseErrors++;
42
- return;
43
- }
44
-
45
- let root: SgRoot;
46
- try {
47
- root = await parseAsync(lang, source);
48
- } catch (err) {
49
- log.warn('Failed to parse file', { file: filePath, error: String(err) });
50
- parseErrors++;
51
- return;
52
- }
53
-
54
- const fp = relative(repoRoot, filePath);
55
-
56
- try {
57
- extractFromFile(root, fp, lang, seen, graph);
58
- } catch (err) {
59
- log.error('Extraction crashed', { file: fp, error: String(err) });
60
- extractErrors++;
61
- }
62
-
63
- try {
64
- // Extract calls into a temporary buffer, then filter noise before pushing
65
- const rawCalls: RawCallSite[] = [];
66
- extractCallsFromFile(root, fp, lang, rawCalls);
67
- for (const call of rawCalls) {
68
- if (!NOISE.has(call.callName)) {
69
- graph.rawCalls.push(call);
70
- }
71
- }
72
- } catch (err) {
73
- log.error('Call extraction crashed', { file: fp, error: String(err) });
74
- extractErrors++;
75
- }
76
- });
77
-
78
- await Promise.all(promises);
79
- }
80
-
81
- return { ...graph, parseErrors, extractErrors };
82
- }
@@ -1,75 +0,0 @@
1
- import { readdirSync } from 'fs';
2
- import { extname, join, relative, resolve } from 'path';
3
- import { isSkippableFile, SKIP_DIRS } from '../shared/filters';
4
- import { log } from '../shared/logger';
5
- import { ensureWithinRoot } from '../shared/safe-path';
6
- import { getLanguage } from './languages';
7
-
8
- /**
9
- * Walk the filesystem and find all supported source files.
10
- * If `filterFiles` is provided, only return those specific files (resolved to absolute paths).
11
- * If `include` patterns are provided, keep only files matching at least one pattern.
12
- * If `exclude` patterns are provided, remove files matching any pattern.
13
- */
14
- export function discoverFiles(
15
- repoDir: string,
16
- filterFiles?: string[],
17
- include?: string[],
18
- exclude?: string[],
19
- ): string[] {
20
- const absRepoDir = resolve(repoDir);
21
-
22
- if (filterFiles) {
23
- return filterFiles
24
- .map((f) => (f.startsWith('/') ? f : join(absRepoDir, f)))
25
- .filter((f) => {
26
- try {
27
- ensureWithinRoot(f, absRepoDir);
28
- return getLanguage(extname(f)) !== null;
29
- } catch (err) {
30
- log.warn('Skipping file outside repository root', { file: f, error: String(err) });
31
- return false;
32
- }
33
- });
34
- }
35
-
36
- let files: string[] = [];
37
- walkFiles(absRepoDir, files);
38
-
39
- // Apply include/exclude filters using Bun.Glob
40
- const hasInclude = include && include.length > 0;
41
- const hasExclude = exclude && exclude.length > 0;
42
-
43
- if (hasInclude || hasExclude) {
44
- const includeGlobs = hasInclude ? include.map((p) => new Bun.Glob(p)) : null;
45
- const excludeGlobs = hasExclude ? exclude.map((p) => new Bun.Glob(p)) : null;
46
-
47
- files = files.filter((absPath) => {
48
- const rel = relative(absRepoDir, absPath);
49
-
50
- // If include patterns exist, file must match at least one
51
- if (includeGlobs && !includeGlobs.some((g) => g.match(rel))) {
52
- return false;
53
- }
54
-
55
- // If exclude patterns exist, file must not match any
56
- if (excludeGlobs && excludeGlobs.some((g) => g.match(rel))) {
57
- return false;
58
- }
59
-
60
- return true;
61
- });
62
- }
63
-
64
- return files;
65
- }
66
-
67
- function walkFiles(dir: string, files: string[]): void {
68
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
69
- if (entry.isDirectory() && !SKIP_DIRS.has(entry.name)) {
70
- walkFiles(join(dir, entry.name), files);
71
- } else if (entry.isFile() && getLanguage(extname(entry.name)) !== null && !isSkippableFile(entry.name)) {
72
- files.push(join(dir, entry.name));
73
- }
74
- }
75
- }
@@ -1,37 +0,0 @@
1
- import type { Lang, SgRoot } from '@ast-grep/napi';
2
- import type { RawCallSite, RawGraph } from '../graph/types';
3
- import { extractCallsFromGeneric, extractGeneric } from './extractors/generic';
4
- import { extractCallsFromPython, extractPython } from './extractors/python';
5
- import { extractCallsFromRuby, extractRuby } from './extractors/ruby';
6
- import { extractCallsFromTypeScript, extractTypeScript } from './extractors/typescript';
7
- import { isTypeScriptLike } from './languages';
8
-
9
- export function extractFromFile(
10
- root: SgRoot,
11
- filePath: string,
12
- lang: Lang | string,
13
- seen: Set<string>,
14
- graph: RawGraph,
15
- ): void {
16
- if (isTypeScriptLike(lang)) {
17
- extractTypeScript(root, filePath, seen, graph, lang);
18
- } else if (lang === 'python') {
19
- extractPython(root, filePath, seen, graph);
20
- } else if (lang === 'ruby') {
21
- extractRuby(root, filePath, seen, graph);
22
- } else {
23
- extractGeneric(root, filePath, lang as string, seen, graph);
24
- }
25
- }
26
-
27
- export function extractCallsFromFile(root: SgRoot, filePath: string, lang: Lang | string, calls: RawCallSite[]): void {
28
- if (isTypeScriptLike(lang)) {
29
- extractCallsFromTypeScript(root, filePath, calls);
30
- } else if (lang === 'python') {
31
- extractCallsFromPython(root, filePath, calls);
32
- } else if (lang === 'ruby') {
33
- extractCallsFromRuby(root, filePath, calls);
34
- } else {
35
- extractCallsFromGeneric(root, filePath, lang as string, calls);
36
- }
37
- }
@@ -1,132 +0,0 @@
1
- import type { SgNode, SgRoot } from '@ast-grep/napi';
2
- import type { RawCallSite, RawGraph } from '../../graph/types';
3
- import { type CallExtractionConfig, extractCalls } from '../../shared/extract-calls';
4
- import { computeContentHash } from '../../shared/file-hash';
5
- import { log } from '../../shared/logger';
6
- import { LANG_KINDS } from '../languages';
7
-
8
- export function extractGeneric(root: SgRoot, fp: string, lang: string, seen: Set<string>, graph: RawGraph): void {
9
- const kinds = LANG_KINDS[lang];
10
- if (!kinds) return;
11
- const rootNode = root.root();
12
-
13
- // Try to extract classes
14
- for (const classKind of [kinds.class, kinds.struct, kinds.interface].filter(Boolean)) {
15
- try {
16
- for (const node of rootNode.findAll({ rule: { kind: classKind } })) {
17
- const name = node.field('name')?.text();
18
- if (!name || seen.has(`c:${fp}:${name}`)) continue;
19
- seen.add(`c:${fp}:${name}`);
20
- graph.classes.push({
21
- name,
22
- file: fp,
23
- line_start: node.range().start.line,
24
- line_end: node.range().end.line,
25
- extends: '',
26
- implements: '',
27
- qualified: `${fp}::${name}`,
28
- content_hash: computeContentHash(node.text()),
29
- });
30
- }
31
- } catch (err) {
32
- log.debug('Generic extraction failed', { file: fp, error: String(err) });
33
- }
34
- }
35
-
36
- // Try to extract functions/methods
37
- // biome-ignore lint/complexity/useLiteralKeys: 'constructor' must use bracket notation to avoid Object.prototype.constructor
38
- for (const funcKind of [kinds.function, kinds.method, kinds['constructor'] as string | undefined].filter(Boolean)) {
39
- try {
40
- for (const node of rootNode.findAll({ rule: { kind: funcKind } })) {
41
- const name = node.field('name')?.text();
42
- if (!name) continue;
43
- const line = node.range().start.line;
44
- if (seen.has(`f:${fp}:${name}:${line}`)) continue;
45
- seen.add(`f:${fp}:${name}:${line}`);
46
-
47
- const classAncestor = node.ancestors().find((a: SgNode) => {
48
- const k = String(a.kind());
49
- return k.includes('class') || k.includes('struct') || k.includes('impl');
50
- });
51
- const className = classAncestor?.field('name')?.text() || '';
52
-
53
- graph.functions.push({
54
- name,
55
- file: fp,
56
- line_start: line,
57
- line_end: node.range().end.line,
58
- params: node.field('parameters')?.text() || '()',
59
- returnType: node.field('return_type')?.text() || '',
60
- kind: className ? 'Method' : 'Function',
61
- className,
62
- qualified: className ? `${fp}::${className}.${name}` : `${fp}::${name}`,
63
- content_hash: computeContentHash(node.text()),
64
- });
65
- }
66
- } catch (err) {
67
- log.debug('Generic extraction failed', { file: fp, error: String(err) });
68
- }
69
- }
70
- }
71
-
72
- /** Shared class-finder for languages using class/struct/impl AST kinds. */
73
- function findEnclosingClassGeneric(node: import('@ast-grep/napi').SgNode): import('@ast-grep/napi').SgNode | null {
74
- return node.ancestors().find((a) => {
75
- const k = String(a.kind());
76
- return k.includes('class') || k.includes('struct') || k.includes('impl');
77
- }) ?? null;
78
- }
79
-
80
- /** Per-language call extraction configs for self/super detection. */
81
- const GENERIC_CONFIGS: Record<string, CallExtractionConfig> = {
82
- java: {
83
- selfPrefixes: ['this.'],
84
- superPrefixes: ['super.'],
85
- findEnclosingClass: findEnclosingClassGeneric,
86
- getParentClass: (classNode) => {
87
- const sc = classNode.children().find((c) => c.kind() === 'superclass');
88
- return sc?.children().find((c) => c.kind() === 'type_identifier')?.text();
89
- },
90
- },
91
- csharp: {
92
- selfPrefixes: ['this.'],
93
- superPrefixes: ['base.'],
94
- findEnclosingClass: findEnclosingClassGeneric,
95
- getParentClass: (classNode) => {
96
- const bl = classNode.children().find((c) => c.kind() === 'base_list');
97
- return bl?.children().find((c) => c.kind() === 'identifier' || c.kind() === 'type_identifier')?.text();
98
- },
99
- },
100
- rust: {
101
- selfPrefixes: ['self.'],
102
- superPrefixes: [],
103
- findEnclosingClass: (node) =>
104
- node.ancestors().find((a) => a.kind() === 'impl_item') ?? null,
105
- },
106
- go: {
107
- selfPrefixes: [],
108
- superPrefixes: [],
109
- findEnclosingClass: findEnclosingClassGeneric,
110
- },
111
- php: {
112
- selfPrefixes: [],
113
- superPrefixes: [],
114
- findEnclosingClass: findEnclosingClassGeneric,
115
- },
116
- };
117
-
118
- /** Fallback config for unknown languages — no self/super detection. */
119
- const FALLBACK_CONFIG: CallExtractionConfig = {
120
- selfPrefixes: [],
121
- superPrefixes: [],
122
- findEnclosingClass: findEnclosingClassGeneric,
123
- };
124
-
125
- /**
126
- * Extract raw call sites from a generic language AST.
127
- * Uses per-language config for self/super detection.
128
- */
129
- export function extractCallsFromGeneric(root: SgRoot, fp: string, lang: string, calls: RawCallSite[]): void {
130
- const config = GENERIC_CONFIGS[lang] ?? FALLBACK_CONFIG;
131
- extractCalls(root.root(), fp, config, calls);
132
- }