ai-mind-map 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +554 -0
- package/dist/change-tracker/change-log.d.ts +160 -0
- package/dist/change-tracker/change-log.d.ts.map +1 -0
- package/dist/change-tracker/change-log.js +507 -0
- package/dist/change-tracker/change-log.js.map +1 -0
- package/dist/change-tracker/diff-engine.d.ts +149 -0
- package/dist/change-tracker/diff-engine.d.ts.map +1 -0
- package/dist/change-tracker/diff-engine.js +530 -0
- package/dist/change-tracker/diff-engine.js.map +1 -0
- package/dist/change-tracker/watcher.d.ts +137 -0
- package/dist/change-tracker/watcher.d.ts.map +1 -0
- package/dist/change-tracker/watcher.js +300 -0
- package/dist/change-tracker/watcher.js.map +1 -0
- package/dist/cli.d.ts +20 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +937 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +38 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +222 -0
- package/dist/config.js.map +1 -0
- package/dist/context/compressor.d.ts +49 -0
- package/dist/context/compressor.d.ts.map +1 -0
- package/dist/context/compressor.js +769 -0
- package/dist/context/compressor.js.map +1 -0
- package/dist/context/progressive-disclosure.d.ts +71 -0
- package/dist/context/progressive-disclosure.d.ts.map +1 -0
- package/dist/context/progressive-disclosure.js +470 -0
- package/dist/context/progressive-disclosure.js.map +1 -0
- package/dist/context/token-budget.d.ts +121 -0
- package/dist/context/token-budget.d.ts.map +1 -0
- package/dist/context/token-budget.js +282 -0
- package/dist/context/token-budget.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +944 -0
- package/dist/index.js.map +1 -0
- package/dist/install.d.ts +66 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +946 -0
- package/dist/install.js.map +1 -0
- package/dist/knowledge-graph/architecture.d.ts +213 -0
- package/dist/knowledge-graph/architecture.d.ts.map +1 -0
- package/dist/knowledge-graph/architecture.js +585 -0
- package/dist/knowledge-graph/architecture.js.map +1 -0
- package/dist/knowledge-graph/cypher.d.ts +113 -0
- package/dist/knowledge-graph/cypher.d.ts.map +1 -0
- package/dist/knowledge-graph/cypher.js +1051 -0
- package/dist/knowledge-graph/cypher.js.map +1 -0
- package/dist/knowledge-graph/dead-code.d.ts +121 -0
- package/dist/knowledge-graph/dead-code.d.ts.map +1 -0
- package/dist/knowledge-graph/dead-code.js +331 -0
- package/dist/knowledge-graph/dead-code.js.map +1 -0
- package/dist/knowledge-graph/flow-analyzer.d.ts +167 -0
- package/dist/knowledge-graph/flow-analyzer.d.ts.map +1 -0
- package/dist/knowledge-graph/flow-analyzer.js +739 -0
- package/dist/knowledge-graph/flow-analyzer.js.map +1 -0
- package/dist/knowledge-graph/graph.d.ts +291 -0
- package/dist/knowledge-graph/graph.d.ts.map +1 -0
- package/dist/knowledge-graph/graph.js +978 -0
- package/dist/knowledge-graph/graph.js.map +1 -0
- package/dist/knowledge-graph/index.d.ts +17 -0
- package/dist/knowledge-graph/index.d.ts.map +1 -0
- package/dist/knowledge-graph/index.js +14 -0
- package/dist/knowledge-graph/index.js.map +1 -0
- package/dist/knowledge-graph/indexer.d.ts +112 -0
- package/dist/knowledge-graph/indexer.d.ts.map +1 -0
- package/dist/knowledge-graph/indexer.js +506 -0
- package/dist/knowledge-graph/indexer.js.map +1 -0
- package/dist/knowledge-graph/pagerank.d.ts +141 -0
- package/dist/knowledge-graph/pagerank.d.ts.map +1 -0
- package/dist/knowledge-graph/pagerank.js +493 -0
- package/dist/knowledge-graph/pagerank.js.map +1 -0
- package/dist/knowledge-graph/parser.d.ts +55 -0
- package/dist/knowledge-graph/parser.d.ts.map +1 -0
- package/dist/knowledge-graph/parser.js +1090 -0
- package/dist/knowledge-graph/parser.js.map +1 -0
- package/dist/knowledge-graph/snapshot.d.ts +107 -0
- package/dist/knowledge-graph/snapshot.d.ts.map +1 -0
- package/dist/knowledge-graph/snapshot.js +435 -0
- package/dist/knowledge-graph/snapshot.js.map +1 -0
- package/dist/memory/decision-log.d.ts +151 -0
- package/dist/memory/decision-log.d.ts.map +1 -0
- package/dist/memory/decision-log.js +482 -0
- package/dist/memory/decision-log.js.map +1 -0
- package/dist/memory/persistent-memory.d.ts +182 -0
- package/dist/memory/persistent-memory.d.ts.map +1 -0
- package/dist/memory/persistent-memory.js +579 -0
- package/dist/memory/persistent-memory.js.map +1 -0
- package/dist/memory/session-memory.d.ts +165 -0
- package/dist/memory/session-memory.d.ts.map +1 -0
- package/dist/memory/session-memory.js +382 -0
- package/dist/memory/session-memory.js.map +1 -0
- package/dist/stress-test.d.ts +10 -0
- package/dist/stress-test.d.ts.map +1 -0
- package/dist/stress-test.js +258 -0
- package/dist/stress-test.js.map +1 -0
- package/dist/tools/advanced-tools.d.ts +32 -0
- package/dist/tools/advanced-tools.d.ts.map +1 -0
- package/dist/tools/advanced-tools.js +480 -0
- package/dist/tools/advanced-tools.js.map +1 -0
- package/dist/tools/change-tools.d.ts +76 -0
- package/dist/tools/change-tools.d.ts.map +1 -0
- package/dist/tools/change-tools.js +93 -0
- package/dist/tools/change-tools.js.map +1 -0
- package/dist/tools/context-tools.d.ts +68 -0
- package/dist/tools/context-tools.d.ts.map +1 -0
- package/dist/tools/context-tools.js +141 -0
- package/dist/tools/context-tools.js.map +1 -0
- package/dist/tools/debug-tools.d.ts +25 -0
- package/dist/tools/debug-tools.d.ts.map +1 -0
- package/dist/tools/debug-tools.js +286 -0
- package/dist/tools/debug-tools.js.map +1 -0
- package/dist/tools/evolving-tools.d.ts +23 -0
- package/dist/tools/evolving-tools.d.ts.map +1 -0
- package/dist/tools/evolving-tools.js +207 -0
- package/dist/tools/evolving-tools.js.map +1 -0
- package/dist/tools/flow-tools.d.ts +24 -0
- package/dist/tools/flow-tools.d.ts.map +1 -0
- package/dist/tools/flow-tools.js +265 -0
- package/dist/tools/flow-tools.js.map +1 -0
- package/dist/tools/graph-tools.d.ts +71 -0
- package/dist/tools/graph-tools.d.ts.map +1 -0
- package/dist/tools/graph-tools.js +165 -0
- package/dist/tools/graph-tools.js.map +1 -0
- package/dist/tools/memory-tools.d.ts +62 -0
- package/dist/tools/memory-tools.d.ts.map +1 -0
- package/dist/tools/memory-tools.js +195 -0
- package/dist/tools/memory-tools.js.map +1 -0
- package/dist/tools/smart-tools.d.ts +23 -0
- package/dist/tools/smart-tools.d.ts.map +1 -0
- package/dist/tools/smart-tools.js +482 -0
- package/dist/tools/smart-tools.js.map +1 -0
- package/dist/tools/snapshot-tools.d.ts +19 -0
- package/dist/tools/snapshot-tools.d.ts.map +1 -0
- package/dist/tools/snapshot-tools.js +149 -0
- package/dist/tools/snapshot-tools.js.map +1 -0
- package/dist/types.d.ts +181 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +45 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/logger.d.ts +59 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +142 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/token-counter.d.ts +51 -0
- package/dist/utils/token-counter.d.ts.map +1 -0
- package/dist/utils/token-counter.js +181 -0
- package/dist/utils/token-counter.js.map +1 -0
- package/install.ps1 +321 -0
- package/install.sh +345 -0
- package/package.json +94 -0
- package/setup.bat +62 -0
|
@@ -0,0 +1,1090 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Mind Map — Tree-sitter AST Parser with Regex Fallback
|
|
3
|
+
*
|
|
4
|
+
* Extracts structural information (functions, classes, methods, interfaces,
|
|
5
|
+
* types, enums, constants, exports, imports) from source code files.
|
|
6
|
+
*
|
|
7
|
+
* Inspired by codebase-memory-mcp (158 languages) and Aider's repo map.
|
|
8
|
+
* Uses tree-sitter grammars for accurate parsing with a regex-based fallback
|
|
9
|
+
* when native bindings are unavailable or parsing fails.
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
12
|
+
import { readFile } from 'node:fs/promises';
|
|
13
|
+
import { extname, basename } from 'node:path';
|
|
14
|
+
// ============================================================
|
|
15
|
+
// Language Registry
|
|
16
|
+
// ============================================================
|
|
17
|
+
/** Maps file extensions to language identifiers */
|
|
18
|
+
const EXTENSION_MAP = {
|
|
19
|
+
'.js': 'javascript',
|
|
20
|
+
'.jsx': 'javascript',
|
|
21
|
+
'.mjs': 'javascript',
|
|
22
|
+
'.cjs': 'javascript',
|
|
23
|
+
'.ts': 'typescript',
|
|
24
|
+
'.tsx': 'typescript',
|
|
25
|
+
'.mts': 'typescript',
|
|
26
|
+
'.cts': 'typescript',
|
|
27
|
+
'.py': 'python',
|
|
28
|
+
'.pyw': 'python',
|
|
29
|
+
'.java': 'java',
|
|
30
|
+
'.go': 'go',
|
|
31
|
+
'.rs': 'rust',
|
|
32
|
+
'.c': 'c',
|
|
33
|
+
'.h': 'c',
|
|
34
|
+
'.cpp': 'cpp',
|
|
35
|
+
'.cxx': 'cpp',
|
|
36
|
+
'.cc': 'cpp',
|
|
37
|
+
'.hpp': 'cpp',
|
|
38
|
+
'.hxx': 'cpp',
|
|
39
|
+
'.cs': 'csharp',
|
|
40
|
+
'.rb': 'ruby',
|
|
41
|
+
'.php': 'php',
|
|
42
|
+
'.sh': 'bash',
|
|
43
|
+
'.bash': 'bash',
|
|
44
|
+
'.zsh': 'bash',
|
|
45
|
+
'.kt': 'kotlin',
|
|
46
|
+
'.kts': 'kotlin',
|
|
47
|
+
'.swift': 'swift',
|
|
48
|
+
'.dart': 'dart',
|
|
49
|
+
'.scala': 'scala',
|
|
50
|
+
'.sc': 'scala',
|
|
51
|
+
'.yaml': 'yaml',
|
|
52
|
+
'.yml': 'yaml',
|
|
53
|
+
'.toml': 'toml',
|
|
54
|
+
'.xml': 'xml',
|
|
55
|
+
'.xaml': 'xaml',
|
|
56
|
+
'.sql': 'sql',
|
|
57
|
+
'.proto': 'protobuf',
|
|
58
|
+
'.graphql': 'graphql',
|
|
59
|
+
'.gql': 'graphql',
|
|
60
|
+
};
|
|
61
|
+
/** Maps language identifiers to tree-sitter grammar package names */
|
|
62
|
+
const GRAMMAR_MAP = {
|
|
63
|
+
javascript: 'tree-sitter-javascript',
|
|
64
|
+
typescript: 'tree-sitter-typescript',
|
|
65
|
+
python: 'tree-sitter-python',
|
|
66
|
+
java: 'tree-sitter-java',
|
|
67
|
+
go: 'tree-sitter-go',
|
|
68
|
+
rust: 'tree-sitter-rust',
|
|
69
|
+
c: 'tree-sitter-c',
|
|
70
|
+
cpp: 'tree-sitter-cpp',
|
|
71
|
+
csharp: 'tree-sitter-c-sharp',
|
|
72
|
+
ruby: 'tree-sitter-ruby',
|
|
73
|
+
php: 'tree-sitter-php',
|
|
74
|
+
bash: 'tree-sitter-bash',
|
|
75
|
+
};
|
|
76
|
+
// ============================================================
|
|
77
|
+
// Helpers
|
|
78
|
+
// ============================================================
|
|
79
|
+
/** Generate a unique deterministic ID for a node */
|
|
80
|
+
export function generateNodeId(filePath, name, type) {
|
|
81
|
+
const hash = createHash('sha256')
|
|
82
|
+
.update(`${filePath}::${name}::${type}`)
|
|
83
|
+
.digest('hex');
|
|
84
|
+
return hash.substring(0, 16);
|
|
85
|
+
}
|
|
86
|
+
/** Generate a content hash for change detection */
|
|
87
|
+
export function generateContentHash(content) {
|
|
88
|
+
return createHash('sha256').update(content).digest('hex').substring(0, 16);
|
|
89
|
+
}
|
|
90
|
+
/** Detect language from file extension */
|
|
91
|
+
export function detectLanguage(filePath) {
|
|
92
|
+
const ext = extname(filePath).toLowerCase();
|
|
93
|
+
return EXTENSION_MAP[ext] ?? null;
|
|
94
|
+
}
|
|
95
|
+
/** Get all supported file extensions */
|
|
96
|
+
export function getSupportedExtensions() {
|
|
97
|
+
return Object.keys(EXTENSION_MAP);
|
|
98
|
+
}
|
|
99
|
+
/** Estimate token count for a string (rough: ~4 chars per token) */
|
|
100
|
+
function estimateTokens(text) {
|
|
101
|
+
return Math.ceil(text.length / 4);
|
|
102
|
+
}
|
|
103
|
+
/** Strip leading comment markers from doc comment lines */
|
|
104
|
+
function cleanDocComment(raw) {
|
|
105
|
+
return raw
|
|
106
|
+
.replace(/^\/\*\*?/m, '')
|
|
107
|
+
.replace(/\*\/$/m, '')
|
|
108
|
+
.replace(/^[ \t]*\*[ \t]?/gm, '')
|
|
109
|
+
.replace(/^[ \t]*\/\/\/? ?/gm, '')
|
|
110
|
+
.replace(/^[ \t]*#[ \t]?/gm, '')
|
|
111
|
+
.trim();
|
|
112
|
+
}
|
|
113
|
+
// ============================================================
|
|
114
|
+
// Tree-sitter Parser (primary)
|
|
115
|
+
// ============================================================
|
|
116
|
+
/** Cached tree-sitter Parser instance and loaded grammars */
|
|
117
|
+
let treeSitterParser = null;
|
|
118
|
+
const loadedGrammars = new Map();
|
|
119
|
+
let treeSitterAvailable = null;
|
|
120
|
+
/** Attempt to load the tree-sitter module and a language grammar */
|
|
121
|
+
async function getTreeSitterParser(language) {
|
|
122
|
+
// If we already determined tree-sitter is unavailable, skip
|
|
123
|
+
if (treeSitterAvailable === false) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
if (!treeSitterParser) {
|
|
128
|
+
const TreeSitter = (await import('tree-sitter')).default;
|
|
129
|
+
treeSitterParser = new TreeSitter();
|
|
130
|
+
treeSitterAvailable = true;
|
|
131
|
+
}
|
|
132
|
+
if (!loadedGrammars.has(language)) {
|
|
133
|
+
const grammarPkg = GRAMMAR_MAP[language];
|
|
134
|
+
if (!grammarPkg)
|
|
135
|
+
return null;
|
|
136
|
+
let grammarModule = await import(grammarPkg);
|
|
137
|
+
let grammar = grammarModule.default ?? grammarModule;
|
|
138
|
+
// TypeScript grammar package exports { typescript, tsx }
|
|
139
|
+
if (language === 'typescript' && grammar.typescript) {
|
|
140
|
+
grammar = grammar.typescript;
|
|
141
|
+
}
|
|
142
|
+
loadedGrammars.set(language, grammar);
|
|
143
|
+
}
|
|
144
|
+
return { parser: treeSitterParser, grammar: loadedGrammars.get(language) };
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
treeSitterAvailable = false;
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** Extract nodes from a tree-sitter AST */
|
|
152
|
+
function extractFromTreeSitter(tree, source, filePath, language) {
|
|
153
|
+
const nodes = [];
|
|
154
|
+
const edges = [];
|
|
155
|
+
const sourceLines = source.split('\n');
|
|
156
|
+
const now = Date.now();
|
|
157
|
+
/** Get text for a node */
|
|
158
|
+
function nodeText(node) {
|
|
159
|
+
return node?.text ?? '';
|
|
160
|
+
}
|
|
161
|
+
/** Find the doc comment preceding a node */
|
|
162
|
+
function findDocComment(node) {
|
|
163
|
+
let prev = node.previousNamedSibling;
|
|
164
|
+
if (!prev) {
|
|
165
|
+
// Check parent's previous sibling for doc comments on methods inside classes
|
|
166
|
+
const parent = node.parent;
|
|
167
|
+
if (parent) {
|
|
168
|
+
// Scan unnamed children before our node
|
|
169
|
+
for (let i = 0; i < parent.namedChildCount; i++) {
|
|
170
|
+
const child = parent.namedChild(i);
|
|
171
|
+
if (child?.id === node.id)
|
|
172
|
+
break;
|
|
173
|
+
prev = child;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (prev && prev.type === 'comment') {
|
|
178
|
+
const text = nodeText(prev);
|
|
179
|
+
if (text.startsWith('/**') || text.startsWith('///') || text.startsWith('# ')) {
|
|
180
|
+
return cleanDocComment(text);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Python: look for expression_statement containing a string immediately after function/class def
|
|
184
|
+
if (language === 'python' && node.namedChildCount > 0) {
|
|
185
|
+
const body = node.childForFieldName('body');
|
|
186
|
+
if (body && body.namedChildCount > 0) {
|
|
187
|
+
const first = body.namedChild(0);
|
|
188
|
+
if (first?.type === 'expression_statement') {
|
|
189
|
+
const strNode = first.namedChild(0);
|
|
190
|
+
if (strNode && (strNode.type === 'string' || strNode.type === 'concatenated_string')) {
|
|
191
|
+
return cleanDocComment(nodeText(strNode).replace(/^['"`]{1,3}|['"`]{1,3}$/g, ''));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
/** Determine visibility from modifiers or naming conventions */
|
|
199
|
+
function getVisibility(node) {
|
|
200
|
+
// Check for explicit modifiers
|
|
201
|
+
const modifiers = collectModifiers(node);
|
|
202
|
+
if (modifiers.includes('public'))
|
|
203
|
+
return 'public';
|
|
204
|
+
if (modifiers.includes('private'))
|
|
205
|
+
return 'private';
|
|
206
|
+
if (modifiers.includes('protected'))
|
|
207
|
+
return 'protected';
|
|
208
|
+
if (modifiers.includes('internal'))
|
|
209
|
+
return 'internal';
|
|
210
|
+
// Python naming convention
|
|
211
|
+
if (language === 'python') {
|
|
212
|
+
const name = node.childForFieldName('name');
|
|
213
|
+
if (name) {
|
|
214
|
+
const nameText = nodeText(name);
|
|
215
|
+
if (nameText.startsWith('__') && !nameText.endsWith('__'))
|
|
216
|
+
return 'private';
|
|
217
|
+
if (nameText.startsWith('_'))
|
|
218
|
+
return 'protected';
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return 'unknown';
|
|
222
|
+
}
|
|
223
|
+
/** Collect modifier keywords from a node */
|
|
224
|
+
function collectModifiers(node) {
|
|
225
|
+
const mods = [];
|
|
226
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
227
|
+
const child = node.child(i);
|
|
228
|
+
if (!child)
|
|
229
|
+
continue;
|
|
230
|
+
const t = child.type;
|
|
231
|
+
if (t === 'public' || t === 'private' || t === 'protected' || t === 'internal' ||
|
|
232
|
+
t === 'static' || t === 'async' || t === 'abstract' || t === 'readonly' ||
|
|
233
|
+
t === 'export' || t === 'default' ||
|
|
234
|
+
t === 'accessibility_modifier' || t === 'modifiers') {
|
|
235
|
+
if (t === 'accessibility_modifier' || t === 'modifiers') {
|
|
236
|
+
mods.push(nodeText(child).trim());
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
mods.push(t);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// Also check for export statement wrapping
|
|
244
|
+
if (node.parent?.type === 'export_statement' || node.parent?.type === 'export_declaration') {
|
|
245
|
+
mods.push('export');
|
|
246
|
+
}
|
|
247
|
+
return mods;
|
|
248
|
+
}
|
|
249
|
+
/** Check if a node is exported */
|
|
250
|
+
function isExported(node) {
|
|
251
|
+
const mods = collectModifiers(node);
|
|
252
|
+
if (mods.includes('export'))
|
|
253
|
+
return true;
|
|
254
|
+
if (node.parent?.type === 'export_statement' || node.parent?.type === 'export_declaration')
|
|
255
|
+
return true;
|
|
256
|
+
// Go: exported if name starts with uppercase
|
|
257
|
+
if (language === 'go') {
|
|
258
|
+
const name = node.childForFieldName('name');
|
|
259
|
+
if (name) {
|
|
260
|
+
const n = nodeText(name);
|
|
261
|
+
return n.length > 0 && n[0] === n[0].toUpperCase() && n[0] !== n[0].toLowerCase();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
/** Extract function parameters */
|
|
267
|
+
function extractParameters(paramsNode) {
|
|
268
|
+
if (!paramsNode)
|
|
269
|
+
return [];
|
|
270
|
+
const params = [];
|
|
271
|
+
for (let i = 0; i < paramsNode.namedChildCount; i++) {
|
|
272
|
+
const param = paramsNode.namedChild(i);
|
|
273
|
+
if (!param)
|
|
274
|
+
continue;
|
|
275
|
+
const pType = param.type;
|
|
276
|
+
// Skip commas, parentheses
|
|
277
|
+
if (pType === ',' || pType === '(' || pType === ')')
|
|
278
|
+
continue;
|
|
279
|
+
let name = '';
|
|
280
|
+
let type = null;
|
|
281
|
+
let defaultValue = null;
|
|
282
|
+
let isOptional = false;
|
|
283
|
+
let isRest = false;
|
|
284
|
+
// Try field-based extraction
|
|
285
|
+
const nameNode = param.childForFieldName('name') ?? param.childForFieldName('pattern');
|
|
286
|
+
if (nameNode) {
|
|
287
|
+
name = nodeText(nameNode);
|
|
288
|
+
}
|
|
289
|
+
else if (param.type === 'identifier') {
|
|
290
|
+
name = nodeText(param);
|
|
291
|
+
}
|
|
292
|
+
else if (param.type === 'rest_pattern' || param.type === 'spread_element' || param.type === 'rest_parameter') {
|
|
293
|
+
isRest = true;
|
|
294
|
+
const inner = param.namedChild(0);
|
|
295
|
+
name = inner ? nodeText(inner) : nodeText(param).replace(/^\.\.\./, '');
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
name = nodeText(param).split(':')[0]?.split('=')[0]?.replace(/^\.\.\./, '').trim() ?? '';
|
|
299
|
+
}
|
|
300
|
+
// Type annotation
|
|
301
|
+
const typeNode = param.childForFieldName('type');
|
|
302
|
+
if (typeNode) {
|
|
303
|
+
type = nodeText(typeNode);
|
|
304
|
+
}
|
|
305
|
+
// Default value
|
|
306
|
+
const valueNode = param.childForFieldName('value') ?? param.childForFieldName('default_value');
|
|
307
|
+
if (valueNode) {
|
|
308
|
+
defaultValue = nodeText(valueNode);
|
|
309
|
+
isOptional = true;
|
|
310
|
+
}
|
|
311
|
+
// Optional marker (TypeScript '?')
|
|
312
|
+
for (let j = 0; j < param.childCount; j++) {
|
|
313
|
+
const ch = param.child(j);
|
|
314
|
+
if (ch && nodeText(ch) === '?') {
|
|
315
|
+
isOptional = true;
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// Prefix '...' detection
|
|
320
|
+
if (name.startsWith('...')) {
|
|
321
|
+
isRest = true;
|
|
322
|
+
name = name.substring(3);
|
|
323
|
+
}
|
|
324
|
+
if (pType === 'rest_parameter' || pType === 'rest_pattern') {
|
|
325
|
+
isRest = true;
|
|
326
|
+
}
|
|
327
|
+
if (name) {
|
|
328
|
+
params.push({ name, type, defaultValue, isOptional, isRest });
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return params;
|
|
332
|
+
}
|
|
333
|
+
/** Build a compact signature string from a node */
|
|
334
|
+
function buildSignature(node, name, nodeType) {
|
|
335
|
+
const mods = collectModifiers(node);
|
|
336
|
+
const parts = [];
|
|
337
|
+
// Prefix modifiers
|
|
338
|
+
const filteredMods = mods.filter(m => ['export', 'async', 'static', 'abstract', 'public', 'private', 'protected'].includes(m));
|
|
339
|
+
if (filteredMods.length > 0) {
|
|
340
|
+
parts.push(filteredMods.join(' '));
|
|
341
|
+
}
|
|
342
|
+
// Keyword
|
|
343
|
+
switch (nodeType) {
|
|
344
|
+
case 'function':
|
|
345
|
+
parts.push('function');
|
|
346
|
+
break;
|
|
347
|
+
case 'class':
|
|
348
|
+
parts.push('class');
|
|
349
|
+
break;
|
|
350
|
+
case 'method':
|
|
351
|
+
parts.push('');
|
|
352
|
+
break; // no keyword for methods
|
|
353
|
+
case 'interface':
|
|
354
|
+
parts.push('interface');
|
|
355
|
+
break;
|
|
356
|
+
case 'type_alias':
|
|
357
|
+
parts.push('type');
|
|
358
|
+
break;
|
|
359
|
+
case 'enum':
|
|
360
|
+
parts.push('enum');
|
|
361
|
+
break;
|
|
362
|
+
case 'constant':
|
|
363
|
+
parts.push('const');
|
|
364
|
+
break;
|
|
365
|
+
case 'variable':
|
|
366
|
+
parts.push('let');
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
parts.push(name);
|
|
370
|
+
// Type parameters (generics)
|
|
371
|
+
const typeParams = node.childForFieldName('type_parameters');
|
|
372
|
+
if (typeParams) {
|
|
373
|
+
parts[parts.length - 1] += nodeText(typeParams);
|
|
374
|
+
}
|
|
375
|
+
// Parameters
|
|
376
|
+
const paramsNode = node.childForFieldName('parameters');
|
|
377
|
+
if (paramsNode) {
|
|
378
|
+
parts[parts.length - 1] += nodeText(paramsNode);
|
|
379
|
+
}
|
|
380
|
+
// Return type
|
|
381
|
+
const returnType = node.childForFieldName('return_type') ?? node.childForFieldName('result');
|
|
382
|
+
if (returnType) {
|
|
383
|
+
parts.push(':');
|
|
384
|
+
parts.push(nodeText(returnType));
|
|
385
|
+
}
|
|
386
|
+
// Superclass / implements
|
|
387
|
+
const superclass = node.childForFieldName('superclass');
|
|
388
|
+
if (superclass) {
|
|
389
|
+
parts.push('extends');
|
|
390
|
+
parts.push(nodeText(superclass));
|
|
391
|
+
}
|
|
392
|
+
return parts.filter(Boolean).join(' ').replace(/\s+/g, ' ').trim();
|
|
393
|
+
}
|
|
394
|
+
/** Create a GraphNode from a tree-sitter node */
|
|
395
|
+
function makeNode(tsNode, name, nType, parentId) {
|
|
396
|
+
const mods = collectModifiers(tsNode);
|
|
397
|
+
const signature = buildSignature(tsNode, name, nType);
|
|
398
|
+
const paramsNode = tsNode.childForFieldName('parameters');
|
|
399
|
+
const returnTypeNode = tsNode.childForFieldName('return_type') ?? tsNode.childForFieldName('result');
|
|
400
|
+
const startLine = tsNode.startPosition.row + 1;
|
|
401
|
+
const endLine = tsNode.endPosition.row + 1;
|
|
402
|
+
const sliceText = sourceLines.slice(startLine - 1, endLine).join('\n');
|
|
403
|
+
const gNode = {
|
|
404
|
+
id: generateNodeId(filePath, parentId ? `${parentId}::${name}` : name, nType),
|
|
405
|
+
type: nType,
|
|
406
|
+
name,
|
|
407
|
+
qualifiedName: parentId ? `${parentId}.${name}` : name,
|
|
408
|
+
filePath,
|
|
409
|
+
startLine,
|
|
410
|
+
endLine,
|
|
411
|
+
signature,
|
|
412
|
+
docComment: findDocComment(tsNode),
|
|
413
|
+
hash: generateContentHash(sliceText),
|
|
414
|
+
language,
|
|
415
|
+
visibility: getVisibility(tsNode),
|
|
416
|
+
isAsync: mods.includes('async'),
|
|
417
|
+
isStatic: mods.includes('static'),
|
|
418
|
+
isExported: isExported(tsNode),
|
|
419
|
+
parameters: paramsNode ? extractParameters(paramsNode) : undefined,
|
|
420
|
+
returnType: returnTypeNode ? nodeText(returnTypeNode).replace(/^:\s*/, '') : undefined,
|
|
421
|
+
updatedAt: now,
|
|
422
|
+
};
|
|
423
|
+
return gNode;
|
|
424
|
+
}
|
|
425
|
+
/** Recursively walk the AST and extract nodes */
|
|
426
|
+
function walk(tsNode, parentClassName) {
|
|
427
|
+
if (!tsNode)
|
|
428
|
+
return;
|
|
429
|
+
const t = tsNode.type;
|
|
430
|
+
const nameNode = tsNode.childForFieldName('name');
|
|
431
|
+
const nameStr = nameNode ? nodeText(nameNode) : '';
|
|
432
|
+
// --- Functions ---
|
|
433
|
+
if (t === 'function_declaration' || t === 'function_definition' ||
|
|
434
|
+
t === 'arrow_function' || t === 'function_item' /* Rust */) {
|
|
435
|
+
const funcName = nameStr || (tsNode.parent?.type === 'variable_declarator'
|
|
436
|
+
? nodeText(tsNode.parent.childForFieldName('name'))
|
|
437
|
+
: `anonymous_${tsNode.startPosition.row}`);
|
|
438
|
+
const gNode = makeNode(tsNode, funcName, 'function');
|
|
439
|
+
nodes.push(gNode);
|
|
440
|
+
}
|
|
441
|
+
// --- Classes ---
|
|
442
|
+
else if (t === 'class_declaration' || t === 'class_definition' ||
|
|
443
|
+
t === 'class' || t === 'struct_item' /* Rust */ ||
|
|
444
|
+
t === 'struct_specifier' || t === 'class_specifier') {
|
|
445
|
+
const className = nameStr || `AnonymousClass_${tsNode.startPosition.row}`;
|
|
446
|
+
const gNode = makeNode(tsNode, className, 'class');
|
|
447
|
+
nodes.push(gNode);
|
|
448
|
+
// Process class body for methods/properties
|
|
449
|
+
const body = tsNode.childForFieldName('body');
|
|
450
|
+
if (body) {
|
|
451
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
452
|
+
const member = body.namedChild(i);
|
|
453
|
+
if (!member)
|
|
454
|
+
continue;
|
|
455
|
+
walkClassMember(member, className, gNode.id);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return; // Don't recurse children again
|
|
459
|
+
}
|
|
460
|
+
// --- Interfaces (TypeScript, Java, Go, C#) ---
|
|
461
|
+
else if (t === 'interface_declaration' || t === 'interface_definition') {
|
|
462
|
+
const gNode = makeNode(tsNode, nameStr, 'interface');
|
|
463
|
+
nodes.push(gNode);
|
|
464
|
+
// Process interface body
|
|
465
|
+
const body = tsNode.childForFieldName('body');
|
|
466
|
+
if (body) {
|
|
467
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
468
|
+
const member = body.namedChild(i);
|
|
469
|
+
if (!member)
|
|
470
|
+
continue;
|
|
471
|
+
walkInterfaceMember(member, nameStr, gNode.id);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
// --- Type aliases ---
|
|
477
|
+
else if (t === 'type_alias_declaration' || t === 'type_alias') {
|
|
478
|
+
const gNode = makeNode(tsNode, nameStr, 'type_alias');
|
|
479
|
+
nodes.push(gNode);
|
|
480
|
+
}
|
|
481
|
+
// --- Enums ---
|
|
482
|
+
else if (t === 'enum_declaration' || t === 'enum_definition' || t === 'enum_item') {
|
|
483
|
+
const gNode = makeNode(tsNode, nameStr, 'enum');
|
|
484
|
+
nodes.push(gNode);
|
|
485
|
+
}
|
|
486
|
+
// --- Variable declarations (constants) ---
|
|
487
|
+
else if (t === 'lexical_declaration' || t === 'variable_declaration') {
|
|
488
|
+
for (let i = 0; i < tsNode.namedChildCount; i++) {
|
|
489
|
+
const declarator = tsNode.namedChild(i);
|
|
490
|
+
if (!declarator || declarator.type !== 'variable_declarator')
|
|
491
|
+
continue;
|
|
492
|
+
const declName = declarator.childForFieldName('name');
|
|
493
|
+
if (!declName)
|
|
494
|
+
continue;
|
|
495
|
+
const varName = nodeText(declName);
|
|
496
|
+
// Check if it's const
|
|
497
|
+
const isConst = nodeText(tsNode).trimStart().startsWith('const');
|
|
498
|
+
const nodeType = isConst ? 'constant' : 'variable';
|
|
499
|
+
// Check if the value is a function (arrow function or function expression)
|
|
500
|
+
const valueNode = declarator.childForFieldName('value');
|
|
501
|
+
if (valueNode && (valueNode.type === 'arrow_function' ||
|
|
502
|
+
valueNode.type === 'function' ||
|
|
503
|
+
valueNode.type === 'function_expression')) {
|
|
504
|
+
const gNode = makeNode(valueNode, varName, 'function');
|
|
505
|
+
// Override signature to include const keyword
|
|
506
|
+
gNode.signature = `${isExported(tsNode) ? 'export ' : ''}const ${varName}${nodeText(valueNode).split('=>')[0]?.includes('(') ? nodeText(valueNode).split('=>')[0]?.trim().replace(/^[^(]*/, '') : '()'}`;
|
|
507
|
+
const paramsNode = valueNode.childForFieldName('parameters');
|
|
508
|
+
if (paramsNode) {
|
|
509
|
+
gNode.parameters = extractParameters(paramsNode);
|
|
510
|
+
}
|
|
511
|
+
gNode.isExported = isExported(tsNode);
|
|
512
|
+
nodes.push(gNode);
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
const gNode = makeNode(tsNode, varName, nodeType);
|
|
516
|
+
gNode.isExported = isExported(tsNode);
|
|
517
|
+
// Build a cleaner signature for variables
|
|
518
|
+
const typeAnn = declarator.childForFieldName('type');
|
|
519
|
+
gNode.signature = `${isExported(tsNode) ? 'export ' : ''}${isConst ? 'const' : 'let'} ${varName}${typeAnn ? ': ' + nodeText(typeAnn) : ''}`;
|
|
520
|
+
if (typeAnn)
|
|
521
|
+
gNode.returnType = nodeText(typeAnn);
|
|
522
|
+
nodes.push(gNode);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
// --- Import statements ---
|
|
527
|
+
else if (t === 'import_statement' || t === 'import_declaration') {
|
|
528
|
+
const sourceNode = tsNode.childForFieldName('source');
|
|
529
|
+
if (sourceNode) {
|
|
530
|
+
const importSource = nodeText(sourceNode).replace(/['"`]/g, '');
|
|
531
|
+
// Create edge from file to imported module
|
|
532
|
+
const fileNodeId = generateNodeId(filePath, basename(filePath), 'file');
|
|
533
|
+
edges.push({
|
|
534
|
+
sourceId: fileNodeId,
|
|
535
|
+
targetId: importSource, // Will be resolved later
|
|
536
|
+
type: 'imports',
|
|
537
|
+
metadata: { raw: nodeText(tsNode).trim() },
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
// --- Export statements ---
|
|
542
|
+
else if (t === 'export_statement' || t === 'export_declaration') {
|
|
543
|
+
// Process the declaration inside the export
|
|
544
|
+
for (let i = 0; i < tsNode.namedChildCount; i++) {
|
|
545
|
+
const child = tsNode.namedChild(i);
|
|
546
|
+
if (child)
|
|
547
|
+
walk(child, parentClassName);
|
|
548
|
+
}
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
// --- Python-specific: decorated definitions ---
|
|
552
|
+
else if (t === 'decorated_definition') {
|
|
553
|
+
// Process the definition inside
|
|
554
|
+
const definition = tsNode.childForFieldName('definition');
|
|
555
|
+
if (definition)
|
|
556
|
+
walk(definition, parentClassName);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
// --- Go specific: function_declaration, method_declaration ---
|
|
560
|
+
else if (t === 'method_declaration') {
|
|
561
|
+
const methodName = nameStr;
|
|
562
|
+
const receiver = tsNode.childForFieldName('receiver');
|
|
563
|
+
const receiverType = receiver ? nodeText(receiver).replace(/[()]/g, '').split(/\s+/).pop() ?? '' : '';
|
|
564
|
+
if (methodName) {
|
|
565
|
+
const gNode = makeNode(tsNode, methodName, 'method', receiverType || undefined);
|
|
566
|
+
nodes.push(gNode);
|
|
567
|
+
if (receiverType) {
|
|
568
|
+
const parentId = generateNodeId(filePath, receiverType, 'class');
|
|
569
|
+
edges.push({ sourceId: parentId, targetId: gNode.id, type: 'contains' });
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
// --- Rust: impl blocks ---
|
|
574
|
+
else if (t === 'impl_item') {
|
|
575
|
+
const implType = tsNode.childForFieldName('type');
|
|
576
|
+
const implName = implType ? nodeText(implType) : '';
|
|
577
|
+
const body = tsNode.childForFieldName('body');
|
|
578
|
+
if (body && implName) {
|
|
579
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
580
|
+
const member = body.namedChild(i);
|
|
581
|
+
if (!member)
|
|
582
|
+
continue;
|
|
583
|
+
if (member.type === 'function_item') {
|
|
584
|
+
const mn = member.childForFieldName('name');
|
|
585
|
+
if (mn) {
|
|
586
|
+
const gNode = makeNode(member, nodeText(mn), 'method', implName);
|
|
587
|
+
nodes.push(gNode);
|
|
588
|
+
const parentId = generateNodeId(filePath, implName, 'class');
|
|
589
|
+
edges.push({ sourceId: parentId, targetId: gNode.id, type: 'contains' });
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
// Recurse into children
|
|
597
|
+
for (let i = 0; i < tsNode.namedChildCount; i++) {
|
|
598
|
+
walk(tsNode.namedChild(i), parentClassName);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
/** Walk class members */
|
|
602
|
+
function walkClassMember(member, className, classId) {
|
|
603
|
+
const mType = member.type;
|
|
604
|
+
const memberNameNode = member.childForFieldName('name');
|
|
605
|
+
const memberName = memberNameNode ? nodeText(memberNameNode) : '';
|
|
606
|
+
if (mType === 'method_definition' || mType === 'method_declaration' ||
|
|
607
|
+
mType === 'function_definition' || mType === 'function_declaration' ||
|
|
608
|
+
mType === 'function_item') {
|
|
609
|
+
if (memberName) {
|
|
610
|
+
const nType = memberName === 'constructor' || memberName === '__init__' ? 'constructor' : 'method';
|
|
611
|
+
const gNode = makeNode(member, memberName, nType, className);
|
|
612
|
+
nodes.push(gNode);
|
|
613
|
+
edges.push({ sourceId: classId, targetId: gNode.id, type: 'contains' });
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
else if (mType === 'public_field_definition' || mType === 'field_definition' ||
|
|
617
|
+
mType === 'property_declaration' || mType === 'field_declaration') {
|
|
618
|
+
if (memberName) {
|
|
619
|
+
const gNode = makeNode(member, memberName, 'property', className);
|
|
620
|
+
nodes.push(gNode);
|
|
621
|
+
edges.push({ sourceId: classId, targetId: gNode.id, type: 'contains' });
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
// Recurse in case of decorated methods, etc.
|
|
626
|
+
if (mType === 'decorated_definition') {
|
|
627
|
+
const def = member.childForFieldName('definition');
|
|
628
|
+
if (def)
|
|
629
|
+
walkClassMember(def, className, classId);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
/** Walk interface members */
|
|
634
|
+
function walkInterfaceMember(member, ifaceName, ifaceId) {
|
|
635
|
+
const memberNameNode = member.childForFieldName('name');
|
|
636
|
+
const memberName = memberNameNode ? nodeText(memberNameNode) : '';
|
|
637
|
+
if (memberName) {
|
|
638
|
+
// Interface methods are treated as method signatures
|
|
639
|
+
const nType = member.childForFieldName('parameters') ? 'method' : 'property';
|
|
640
|
+
const gNode = makeNode(member, memberName, nType, ifaceName);
|
|
641
|
+
nodes.push(gNode);
|
|
642
|
+
edges.push({ sourceId: ifaceId, targetId: gNode.id, type: 'contains' });
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
// Create the file node itself
|
|
646
|
+
const fileNode = {
|
|
647
|
+
id: generateNodeId(filePath, basename(filePath), 'file'),
|
|
648
|
+
type: 'file',
|
|
649
|
+
name: basename(filePath),
|
|
650
|
+
qualifiedName: filePath,
|
|
651
|
+
filePath,
|
|
652
|
+
startLine: 1,
|
|
653
|
+
endLine: sourceLines.length,
|
|
654
|
+
signature: filePath,
|
|
655
|
+
docComment: null,
|
|
656
|
+
hash: generateContentHash(source),
|
|
657
|
+
language,
|
|
658
|
+
visibility: 'public',
|
|
659
|
+
isAsync: false,
|
|
660
|
+
isStatic: false,
|
|
661
|
+
isExported: false,
|
|
662
|
+
updatedAt: now,
|
|
663
|
+
};
|
|
664
|
+
nodes.push(fileNode);
|
|
665
|
+
// Walk the AST
|
|
666
|
+
walk(tree.rootNode);
|
|
667
|
+
// Create 'contains' edges from file to all top-level symbols
|
|
668
|
+
for (const node of nodes) {
|
|
669
|
+
if (node.type !== 'file' && node.qualifiedName === node.name) {
|
|
670
|
+
edges.push({ sourceId: fileNode.id, targetId: node.id, type: 'contains' });
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return { nodes, edges };
|
|
674
|
+
}
|
|
675
|
+
/** Language-specific regex patterns for fallback parsing */
|
|
676
|
+
const REGEX_PATTERNS = {
|
|
677
|
+
javascript: [
|
|
678
|
+
{ pattern: /^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*(\([^)]*\))/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
|
|
679
|
+
{ pattern: /^(?:export\s+)?class\s+(\w+)(?:\s+extends\s+\w+)?(?:\s+implements\s+[\w,\s]+)?\s*\{/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
680
|
+
{ pattern: /^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
|
|
681
|
+
{ pattern: /^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*function/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
|
|
682
|
+
{ pattern: /^\s+(?:async\s+)?(\w+)\s*(\([^)]*\))\s*\{/gm, type: 'method', nameGroup: 1, signatureGroup: 0 },
|
|
683
|
+
],
|
|
684
|
+
typescript: [
|
|
685
|
+
{ pattern: /^(?:export\s+)?(?:async\s+)?function\s+(\w+)(?:<[^>]*>)?\s*(\([^)]*\))(?:\s*:\s*[\w<>\[\]|&\s]+)?/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
|
|
686
|
+
{ pattern: /^(?:export\s+)?(?:abstract\s+)?class\s+(\w+)(?:<[^>]*>)?(?:\s+extends\s+[\w<>,\s]+)?(?:\s+implements\s+[\w<>,\s]+)?\s*\{/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
687
|
+
{ pattern: /^(?:export\s+)?interface\s+(\w+)(?:<[^>]*>)?(?:\s+extends\s+[\w<>,\s]+)?\s*\{/gm, type: 'interface', nameGroup: 1, signatureGroup: 0 },
|
|
688
|
+
{ pattern: /^(?:export\s+)?type\s+(\w+)(?:<[^>]*>)?\s*=/gm, type: 'type_alias', nameGroup: 1, signatureGroup: 0 },
|
|
689
|
+
{ pattern: /^(?:export\s+)?enum\s+(\w+)\s*\{/gm, type: 'enum', nameGroup: 1, signatureGroup: 0 },
|
|
690
|
+
{ pattern: /^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*(?::\s*[\w<>\[\]|&\s]+)?\s*=\s*(?:async\s+)?(?:\([^)]*\)|[\w]+)\s*(?::\s*[\w<>\[\]|&\s]+)?\s*=>/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
|
|
691
|
+
{ pattern: /^\s+(?:public|private|protected)?\s*(?:static\s+)?(?:async\s+)?(\w+)\s*(\([^)]*\))/gm, type: 'method', nameGroup: 1, signatureGroup: 0 },
|
|
692
|
+
],
|
|
693
|
+
python: [
|
|
694
|
+
{ pattern: /^(?:async\s+)?def\s+(\w+)\s*(\([^)]*\))(?:\s*->\s*[\w\[\],\s|]+)?:/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
|
|
695
|
+
{ pattern: /^class\s+(\w+)(?:\([^)]*\))?\s*:/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
696
|
+
{ pattern: /^\s+(?:async\s+)?def\s+(\w+)\s*\(self[^)]*\)(?:\s*->\s*[\w\[\],\s|]+)?:/gm, type: 'method', nameGroup: 1, signatureGroup: 0 },
|
|
697
|
+
{ pattern: /^\s+@(?:staticmethod|classmethod)\s*\n\s+def\s+(\w+)\s*(\([^)]*\))/gm, type: 'method', nameGroup: 1, signatureGroup: 0 },
|
|
698
|
+
],
|
|
699
|
+
java: [
|
|
700
|
+
{ pattern: /(?:public|private|protected)\s+(?:static\s+)?(?:final\s+)?(?:abstract\s+)?class\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
701
|
+
{ pattern: /(?:public|private|protected)\s+(?:static\s+)?interface\s+(\w+)/gm, type: 'interface', nameGroup: 1, signatureGroup: 0 },
|
|
702
|
+
{ pattern: /(?:public|private|protected)\s+(?:static\s+)?(?:final\s+)?(?:abstract\s+)?(?:synchronized\s+)?[\w<>\[\],\s]+\s+(\w+)\s*(\([^)]*\))/gm, type: 'method', nameGroup: 1, signatureGroup: 0 },
|
|
703
|
+
{ pattern: /(?:public|private|protected)\s+(?:static\s+)?enum\s+(\w+)/gm, type: 'enum', nameGroup: 1, signatureGroup: 0 },
|
|
704
|
+
],
|
|
705
|
+
go: [
|
|
706
|
+
{ pattern: /^func\s+(\w+)\s*(\([^)]*\))(?:\s*(?:\([^)]*\)|[\w*]+))?\s*\{/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
|
|
707
|
+
{ pattern: /^func\s+\([^)]+\)\s+(\w+)\s*(\([^)]*\))(?:\s*(?:\([^)]*\)|[\w*]+))?\s*\{/gm, type: 'method', nameGroup: 1, signatureGroup: 0 },
|
|
708
|
+
{ pattern: /^type\s+(\w+)\s+struct\s*\{/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
709
|
+
{ pattern: /^type\s+(\w+)\s+interface\s*\{/gm, type: 'interface', nameGroup: 1, signatureGroup: 0 },
|
|
710
|
+
],
|
|
711
|
+
rust: [
|
|
712
|
+
{ pattern: /^(?:pub(?:\([^)]*\))?\s+)?(?:async\s+)?fn\s+(\w+)(?:<[^>]*>)?\s*(\([^)]*\))(?:\s*->\s*[\w<>&\[\]]+)?\s*(?:where[^{]*)?\{/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
|
|
713
|
+
{ pattern: /^(?:pub(?:\([^)]*\))?\s+)?struct\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
714
|
+
{ pattern: /^(?:pub(?:\([^)]*\))?\s+)?enum\s+(\w+)/gm, type: 'enum', nameGroup: 1, signatureGroup: 0 },
|
|
715
|
+
{ pattern: /^(?:pub(?:\([^)]*\))?\s+)?trait\s+(\w+)/gm, type: 'interface', nameGroup: 1, signatureGroup: 0 },
|
|
716
|
+
{ pattern: /^\s+(?:pub(?:\([^)]*\))?\s+)?(?:async\s+)?fn\s+(\w+)(?:<[^>]*>)?\s*\(&?(?:mut\s+)?self[^)]*\)/gm, type: 'method', nameGroup: 1, signatureGroup: 0 },
|
|
717
|
+
],
|
|
718
|
+
c: [
|
|
719
|
+
{ pattern: /^(?:static\s+)?(?:inline\s+)?(?:extern\s+)?(?:const\s+)?[\w*\s]+\s+(\w+)\s*(\([^)]*\))\s*\{/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
|
|
720
|
+
{ pattern: /^(?:typedef\s+)?struct\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
721
|
+
{ pattern: /^(?:typedef\s+)?enum\s+(\w+)/gm, type: 'enum', nameGroup: 1, signatureGroup: 0 },
|
|
722
|
+
],
|
|
723
|
+
cpp: [
|
|
724
|
+
{ pattern: /^(?:(?:virtual|static|inline|explicit|extern|const|constexpr)\s+)*[\w:<>*&\s]+\s+(\w+)\s*(\([^)]*\))(?:\s*(?:const|override|final|noexcept))*\s*\{/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
|
|
725
|
+
{ pattern: /^(?:template\s*<[^>]*>\s*)?class\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
726
|
+
{ pattern: /^(?:template\s*<[^>]*>\s*)?struct\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
727
|
+
{ pattern: /^namespace\s+(\w+)\s*\{/gm, type: 'namespace', nameGroup: 1, signatureGroup: 0 },
|
|
728
|
+
],
|
|
729
|
+
csharp: [
|
|
730
|
+
{ pattern: /(?:public|private|protected|internal)\s+(?:static\s+)?(?:partial\s+)?(?:abstract\s+)?class\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
731
|
+
{ pattern: /(?:public|private|protected|internal)\s+(?:static\s+)?interface\s+(\w+)/gm, type: 'interface', nameGroup: 1, signatureGroup: 0 },
|
|
732
|
+
{ pattern: /(?:public|private|protected|internal)\s+(?:static\s+)?(?:virtual\s+)?(?:override\s+)?(?:async\s+)?[\w<>\[\],?\s]+\s+(\w+)\s*(\([^)]*\))/gm, type: 'method', nameGroup: 1, signatureGroup: 0 },
|
|
733
|
+
{ pattern: /(?:public|private|protected|internal)\s+(?:static\s+)?enum\s+(\w+)/gm, type: 'enum', nameGroup: 1, signatureGroup: 0 },
|
|
734
|
+
{ pattern: /^namespace\s+([\w.]+)/gm, type: 'namespace', nameGroup: 1, signatureGroup: 0 },
|
|
735
|
+
],
|
|
736
|
+
ruby: [
|
|
737
|
+
{ pattern: /^\s*def\s+(?:self\.)?(\w+[?!]?)(?:\(([^)]*)\))?/gm, type: 'method', nameGroup: 1, signatureGroup: 0 },
|
|
738
|
+
{ pattern: /^\s*class\s+(\w+)(?:\s*<\s*\w+)?/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
739
|
+
{ pattern: /^\s*module\s+(\w+)/gm, type: 'module', nameGroup: 1, signatureGroup: 0 },
|
|
740
|
+
],
|
|
741
|
+
php: [
|
|
742
|
+
{ pattern: /(?:public|private|protected)?\s*(?:static\s+)?function\s+(\w+)\s*(\([^)]*\))/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
|
|
743
|
+
{ pattern: /(?:abstract\s+)?class\s+(\w+)(?:\s+extends\s+\w+)?(?:\s+implements\s+[\w,\s]+)?/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
744
|
+
{ pattern: /interface\s+(\w+)(?:\s+extends\s+[\w,\s]+)?/gm, type: 'interface', nameGroup: 1, signatureGroup: 0 },
|
|
745
|
+
{ pattern: /trait\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
746
|
+
],
|
|
747
|
+
bash: [
|
|
748
|
+
{ pattern: /^(?:function\s+)?(\w+)\s*\(\)\s*\{/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
|
|
749
|
+
{ pattern: /^(\w+)\s*=\s*/gm, type: 'variable', nameGroup: 1, signatureGroup: 0 },
|
|
750
|
+
],
|
|
751
|
+
kotlin: [
|
|
752
|
+
{ pattern: /(?:public|private|protected|internal)?\s*(?:data\s+|sealed\s+|abstract\s+|open\s+|inner\s+)?class\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
753
|
+
{ pattern: /(?:public|private|protected|internal)?\s*(?:fun)\s+(?:<[^>]*>\s+)?(\w+)\s*(\([^)]*\))/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
|
|
754
|
+
{ pattern: /(?:public|private|protected|internal)?\s*interface\s+(\w+)/gm, type: 'interface', nameGroup: 1, signatureGroup: 0 },
|
|
755
|
+
{ pattern: /(?:public|private|protected|internal)?\s*(?:enum\s+class|enum)\s+(\w+)/gm, type: 'enum', nameGroup: 1, signatureGroup: 0 },
|
|
756
|
+
{ pattern: /(?:public|private|protected|internal)?\s*object\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
757
|
+
{ pattern: /(?:public|private|protected|internal)?\s*(?:val|var)\s+(\w+)\s*(?::\s*[\w<>\[\]?,\s]+)?\s*(?:=|$)/gm, type: 'variable', nameGroup: 1, signatureGroup: 0 },
|
|
758
|
+
{ pattern: /(?:public|private|protected|internal)?\s*typealias\s+(\w+)/gm, type: 'type_alias', nameGroup: 1, signatureGroup: 0 },
|
|
759
|
+
{ pattern: /^package\s+([\w.]+)/gm, type: 'namespace', nameGroup: 1, signatureGroup: 0 },
|
|
760
|
+
],
|
|
761
|
+
swift: [
|
|
762
|
+
{ pattern: /(?:public|private|fileprivate|internal|open)?\s*(?:final\s+)?class\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
763
|
+
{ pattern: /(?:public|private|fileprivate|internal|open)?\s*struct\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
764
|
+
{ pattern: /(?:public|private|fileprivate|internal|open)?\s*(?:static\s+|class\s+)?func\s+(\w+)\s*(\([^)]*\))/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
|
|
765
|
+
{ pattern: /(?:public|private|fileprivate|internal|open)?\s*protocol\s+(\w+)/gm, type: 'interface', nameGroup: 1, signatureGroup: 0 },
|
|
766
|
+
{ pattern: /(?:public|private|fileprivate|internal|open)?\s*enum\s+(\w+)/gm, type: 'enum', nameGroup: 1, signatureGroup: 0 },
|
|
767
|
+
{ pattern: /(?:public|private|fileprivate|internal|open)?\s*extension\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
768
|
+
{ pattern: /(?:public|private|fileprivate|internal|open)?\s*typealias\s+(\w+)/gm, type: 'type_alias', nameGroup: 1, signatureGroup: 0 },
|
|
769
|
+
],
|
|
770
|
+
dart: [
|
|
771
|
+
{ pattern: /(?:abstract\s+)?class\s+(\w+)(?:<[^>]*>)?(?:\s+extends\s+[\w<>,\s]+)?(?:\s+(?:with|implements)\s+[\w<>,\s]+)?\s*\{/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
772
|
+
{ pattern: /(?:Future<[^>]*>|void|int|double|bool|String|dynamic|[\w<>]+)\s+(\w+)\s*(\([^)]*\))\s*(?:async\s*)?\{/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
|
|
773
|
+
{ pattern: /enum\s+(\w+)\s*\{/gm, type: 'enum', nameGroup: 1, signatureGroup: 0 },
|
|
774
|
+
{ pattern: /mixin\s+(\w+)(?:\s+on\s+[\w<>,\s]+)?\s*\{/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
775
|
+
{ pattern: /extension\s+(\w+)\s+on\s+[\w<>,\s]+\s*\{/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
776
|
+
{ pattern: /typedef\s+(\w+)/gm, type: 'type_alias', nameGroup: 1, signatureGroup: 0 },
|
|
777
|
+
],
|
|
778
|
+
scala: [
|
|
779
|
+
{ pattern: /(?:sealed\s+|abstract\s+|final\s+)?(?:case\s+)?class\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
780
|
+
{ pattern: /(?:private\s*(?:\[\w+\])?\s*|protected\s*(?:\[\w+\])?\s*)?def\s+(\w+)(?:\[.*?\])?\s*(\([^)]*\))/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
|
|
781
|
+
{ pattern: /trait\s+(\w+)/gm, type: 'interface', nameGroup: 1, signatureGroup: 0 },
|
|
782
|
+
{ pattern: /object\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
783
|
+
{ pattern: /(?:val|var)\s+(\w+)\s*(?::\s*[\w\[\]<>,\s]+)?\s*=/gm, type: 'variable', nameGroup: 1, signatureGroup: 0 },
|
|
784
|
+
{ pattern: /type\s+(\w+)/gm, type: 'type_alias', nameGroup: 1, signatureGroup: 0 },
|
|
785
|
+
{ pattern: /^package\s+([\w.]+)/gm, type: 'namespace', nameGroup: 1, signatureGroup: 0 },
|
|
786
|
+
],
|
|
787
|
+
yaml: [
|
|
788
|
+
{ pattern: /^(\w[\w-]*)\s*:/gm, type: 'variable', nameGroup: 1, signatureGroup: 0 },
|
|
789
|
+
],
|
|
790
|
+
toml: [
|
|
791
|
+
{ pattern: /^\[([\w.-]+)\]/gm, type: 'namespace', nameGroup: 1, signatureGroup: 0 },
|
|
792
|
+
{ pattern: /^(\w[\w-]*)\s*=/gm, type: 'variable', nameGroup: 1, signatureGroup: 0 },
|
|
793
|
+
],
|
|
794
|
+
xml: [
|
|
795
|
+
{ pattern: /<(\w+:[\w.]+|\w+\.\w+)[^>]*(?:x:Class|x:Name)\s*=\s*"([^"]+)"/gm, type: 'class', nameGroup: 2, signatureGroup: 0 },
|
|
796
|
+
{ pattern: /<(Window|Page|UserControl|ResourceDictionary|Application)[\s>]/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
797
|
+
],
|
|
798
|
+
xaml: [
|
|
799
|
+
{ pattern: /x:Class\s*=\s*"([^"]+)"/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
800
|
+
{ pattern: /x:Name\s*=\s*"([^"]+)"/gm, type: 'variable', nameGroup: 1, signatureGroup: 0 },
|
|
801
|
+
{ pattern: /x:Key\s*=\s*"([^"]+)"/gm, type: 'variable', nameGroup: 1, signatureGroup: 0 },
|
|
802
|
+
{ pattern: /(?:Click|Command|Loaded|Closing|TextChanged|SelectionChanged)\s*=\s*"([^"]+)"/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
|
|
803
|
+
{ pattern: /<Style[^>]*TargetType\s*=\s*"\{?x:Type\s+([\w:]+)\}?"/gm, type: 'type_alias', nameGroup: 1, signatureGroup: 0 },
|
|
804
|
+
],
|
|
805
|
+
sql: [
|
|
806
|
+
{ pattern: /CREATE\s+(?:OR\s+REPLACE\s+)?TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:`?\w+`?\.)?`?(\w+)`?/gim, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
807
|
+
{ pattern: /CREATE\s+(?:OR\s+REPLACE\s+)?(?:FUNCTION|PROCEDURE)\s+(?:`?\w+`?\.)?`?(\w+)`?\s*(\([^)]*\))/gim, type: 'function', nameGroup: 1, signatureGroup: 0 },
|
|
808
|
+
{ pattern: /CREATE\s+(?:OR\s+REPLACE\s+)?VIEW\s+(?:`?\w+`?\.)?`?(\w+)`?/gim, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
809
|
+
{ pattern: /CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?`?(\w+)`?/gim, type: 'variable', nameGroup: 1, signatureGroup: 0 },
|
|
810
|
+
{ pattern: /CREATE\s+TRIGGER\s+(?:IF\s+NOT\s+EXISTS\s+)?`?(\w+)`?/gim, type: 'function', nameGroup: 1, signatureGroup: 0 },
|
|
811
|
+
],
|
|
812
|
+
protobuf: [
|
|
813
|
+
{ pattern: /message\s+(\w+)\s*\{/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
814
|
+
{ pattern: /service\s+(\w+)\s*\{/gm, type: 'interface', nameGroup: 1, signatureGroup: 0 },
|
|
815
|
+
{ pattern: /rpc\s+(\w+)\s*(\([^)]*\))/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
|
|
816
|
+
{ pattern: /enum\s+(\w+)\s*\{/gm, type: 'enum', nameGroup: 1, signatureGroup: 0 },
|
|
817
|
+
],
|
|
818
|
+
graphql: [
|
|
819
|
+
{ pattern: /type\s+(\w+)(?:\s+implements\s+[\w&\s]+)?\s*\{/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
820
|
+
{ pattern: /input\s+(\w+)\s*\{/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
|
|
821
|
+
{ pattern: /interface\s+(\w+)\s*\{/gm, type: 'interface', nameGroup: 1, signatureGroup: 0 },
|
|
822
|
+
{ pattern: /enum\s+(\w+)\s*\{/gm, type: 'enum', nameGroup: 1, signatureGroup: 0 },
|
|
823
|
+
{ pattern: /scalar\s+(\w+)/gm, type: 'type_alias', nameGroup: 1, signatureGroup: 0 },
|
|
824
|
+
{ pattern: /(?:query|mutation|subscription)\s+(\w+)/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
|
|
825
|
+
],
|
|
826
|
+
};
|
|
827
|
+
/** Detect doc comment above a line index */
|
|
828
|
+
function findRegexDocComment(lines, lineIndex) {
|
|
829
|
+
// Look backwards for comment blocks
|
|
830
|
+
let commentLines = [];
|
|
831
|
+
for (let i = lineIndex - 1; i >= 0 && i >= lineIndex - 30; i--) {
|
|
832
|
+
const line = lines[i].trim();
|
|
833
|
+
if (line === '') {
|
|
834
|
+
if (commentLines.length > 0)
|
|
835
|
+
break;
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
if (line.startsWith('/**') || line.startsWith('*') || line.startsWith('*/') ||
|
|
839
|
+
line.startsWith('//') || line.startsWith('///') ||
|
|
840
|
+
line.startsWith('#') || line.startsWith('"""') || line.startsWith("'''")) {
|
|
841
|
+
commentLines.unshift(lines[i]);
|
|
842
|
+
}
|
|
843
|
+
else {
|
|
844
|
+
break;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
if (commentLines.length === 0)
|
|
848
|
+
return null;
|
|
849
|
+
return cleanDocComment(commentLines.join('\n'));
|
|
850
|
+
}
|
|
851
|
+
/** Regex-based fallback parser */
|
|
852
|
+
function parseWithRegex(source, filePath, language) {
|
|
853
|
+
const nodes = [];
|
|
854
|
+
const edges = [];
|
|
855
|
+
const lines = source.split('\n');
|
|
856
|
+
const now = Date.now();
|
|
857
|
+
// Create file node
|
|
858
|
+
const fileNode = {
|
|
859
|
+
id: generateNodeId(filePath, basename(filePath), 'file'),
|
|
860
|
+
type: 'file',
|
|
861
|
+
name: basename(filePath),
|
|
862
|
+
qualifiedName: filePath,
|
|
863
|
+
filePath,
|
|
864
|
+
startLine: 1,
|
|
865
|
+
endLine: lines.length,
|
|
866
|
+
signature: filePath,
|
|
867
|
+
docComment: null,
|
|
868
|
+
hash: generateContentHash(source),
|
|
869
|
+
language,
|
|
870
|
+
visibility: 'public',
|
|
871
|
+
isAsync: false,
|
|
872
|
+
isStatic: false,
|
|
873
|
+
isExported: false,
|
|
874
|
+
updatedAt: now,
|
|
875
|
+
};
|
|
876
|
+
nodes.push(fileNode);
|
|
877
|
+
const patterns = REGEX_PATTERNS[language] ?? REGEX_PATTERNS['javascript'] ?? [];
|
|
878
|
+
// Track matched names to avoid duplicates
|
|
879
|
+
const seen = new Set();
|
|
880
|
+
for (const { pattern, type, nameGroup } of patterns) {
|
|
881
|
+
// Reset regex lastIndex
|
|
882
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
883
|
+
let match;
|
|
884
|
+
while ((match = regex.exec(source)) !== null) {
|
|
885
|
+
const name = match[nameGroup];
|
|
886
|
+
if (!name)
|
|
887
|
+
continue;
|
|
888
|
+
const key = `${name}::${type}`;
|
|
889
|
+
if (seen.has(key))
|
|
890
|
+
continue;
|
|
891
|
+
seen.add(key);
|
|
892
|
+
// Calculate line number from match index
|
|
893
|
+
const startLine = source.substring(0, match.index).split('\n').length;
|
|
894
|
+
const matchText = match[0];
|
|
895
|
+
const endLine = startLine + matchText.split('\n').length - 1;
|
|
896
|
+
// Detect modifiers from signature
|
|
897
|
+
const sig = matchText.trim();
|
|
898
|
+
const isAsync = /\basync\b/.test(sig);
|
|
899
|
+
const isStatic = /\bstatic\b/.test(sig);
|
|
900
|
+
const isExportedMatch = /\bexport\b/.test(sig);
|
|
901
|
+
const isPublic = /\bpublic\b/.test(sig);
|
|
902
|
+
const isPrivate = /\bprivate\b/.test(sig);
|
|
903
|
+
const isProtected = /\bprotected\b/.test(sig);
|
|
904
|
+
let visibility = 'unknown';
|
|
905
|
+
if (isPublic)
|
|
906
|
+
visibility = 'public';
|
|
907
|
+
else if (isPrivate)
|
|
908
|
+
visibility = 'private';
|
|
909
|
+
else if (isProtected)
|
|
910
|
+
visibility = 'protected';
|
|
911
|
+
// Python convention
|
|
912
|
+
if (language === 'python' && name.startsWith('__') && !name.endsWith('__'))
|
|
913
|
+
visibility = 'private';
|
|
914
|
+
else if (language === 'python' && name.startsWith('_'))
|
|
915
|
+
visibility = 'protected';
|
|
916
|
+
// Go convention
|
|
917
|
+
if (language === 'go' && name[0] === name[0].toUpperCase() && name[0] !== name[0].toLowerCase())
|
|
918
|
+
visibility = 'public';
|
|
919
|
+
// Build a clean signature without body
|
|
920
|
+
const cleanSig = sig.replace(/\{[\s\S]*$/, '').replace(/:\s*$/, '').trim();
|
|
921
|
+
const gNode = {
|
|
922
|
+
id: generateNodeId(filePath, name, type),
|
|
923
|
+
type,
|
|
924
|
+
name,
|
|
925
|
+
qualifiedName: name,
|
|
926
|
+
filePath,
|
|
927
|
+
startLine,
|
|
928
|
+
endLine,
|
|
929
|
+
signature: cleanSig,
|
|
930
|
+
docComment: findRegexDocComment(lines, startLine - 1),
|
|
931
|
+
hash: generateContentHash(matchText),
|
|
932
|
+
language,
|
|
933
|
+
visibility,
|
|
934
|
+
isAsync,
|
|
935
|
+
isStatic,
|
|
936
|
+
isExported: isExportedMatch,
|
|
937
|
+
updatedAt: now,
|
|
938
|
+
};
|
|
939
|
+
nodes.push(gNode);
|
|
940
|
+
edges.push({ sourceId: fileNode.id, targetId: gNode.id, type: 'contains' });
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
// Extract import edges
|
|
944
|
+
const importPatterns = {
|
|
945
|
+
javascript: /(?:import\s+.*?\s+from\s+['"]([^'"]+)['"]|require\s*\(\s*['"]([^'"]+)['"]\s*\))/gm,
|
|
946
|
+
typescript: /(?:import\s+.*?\s+from\s+['"]([^'"]+)['"]|import\s*\(\s*['"]([^'"]+)['"]\s*\))/gm,
|
|
947
|
+
python: /(?:from\s+([\w.]+)\s+import|import\s+([\w.]+))/gm,
|
|
948
|
+
java: /import\s+(?:static\s+)?([\w.]+);/gm,
|
|
949
|
+
go: /import\s+(?:\(\s*(?:"([^"]+)"[\s]*)+\)|"([^"]+)")/gm,
|
|
950
|
+
rust: /use\s+([\w:]+)/gm,
|
|
951
|
+
csharp: /using\s+(?:static\s+)?([\w.]+);/gm,
|
|
952
|
+
ruby: /require(?:_relative)?\s+['"]([^'"]+)['"]/gm,
|
|
953
|
+
php: /(?:use\s+([\w\\]+)|require(?:_once)?\s+['"]([^'"]+)['"]|include(?:_once)?\s+['"]([^'"]+)['"])/gm,
|
|
954
|
+
c: /#include\s+[<"]([^>"]+)[>"]/gm,
|
|
955
|
+
cpp: /#include\s+[<"]([^>"]+)[>"]/gm,
|
|
956
|
+
bash: /(?:source|\.)\s+['"]?([^'";\s]+)['"]?/gm,
|
|
957
|
+
kotlin: /import\s+([\w.]+)/gm,
|
|
958
|
+
swift: /import\s+(\w+)/gm,
|
|
959
|
+
dart: /import\s+['"]([^'"]+)['"]/gm,
|
|
960
|
+
scala: /import\s+([\w.{}]+)/gm,
|
|
961
|
+
sql: /-- no imports/gm,
|
|
962
|
+
};
|
|
963
|
+
const importRegex = importPatterns[language];
|
|
964
|
+
if (importRegex) {
|
|
965
|
+
const regex = new RegExp(importRegex.source, importRegex.flags);
|
|
966
|
+
let match;
|
|
967
|
+
while ((match = regex.exec(source)) !== null) {
|
|
968
|
+
const importSource = match[1] ?? match[2] ?? match[3] ?? '';
|
|
969
|
+
if (importSource) {
|
|
970
|
+
edges.push({
|
|
971
|
+
sourceId: fileNode.id,
|
|
972
|
+
targetId: importSource,
|
|
973
|
+
type: 'imports',
|
|
974
|
+
metadata: { raw: match[0].trim() },
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return { nodes, edges };
|
|
980
|
+
}
|
|
981
|
+
// ============================================================
|
|
982
|
+
// Public API
|
|
983
|
+
// ============================================================
|
|
984
|
+
/**
|
|
985
|
+
* Parse a source code file and extract its structural information.
|
|
986
|
+
*
|
|
987
|
+
* Attempts tree-sitter parsing first, falls back to regex if unavailable.
|
|
988
|
+
*
|
|
989
|
+
* @param filePath - Absolute path to the source file
|
|
990
|
+
* @param source - Optional pre-read source code (reads from disk if not provided)
|
|
991
|
+
* @returns ParseResult with extracted nodes, edges, and any parse errors
|
|
992
|
+
*/
|
|
993
|
+
export async function parseFile(filePath, source) {
|
|
994
|
+
const language = detectLanguage(filePath);
|
|
995
|
+
if (!language) {
|
|
996
|
+
return {
|
|
997
|
+
filePath,
|
|
998
|
+
language: 'unknown',
|
|
999
|
+
nodes: [],
|
|
1000
|
+
edges: [],
|
|
1001
|
+
parseErrors: [`Unsupported file extension: ${extname(filePath)}`],
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
let content;
|
|
1005
|
+
try {
|
|
1006
|
+
content = source ?? await readFile(filePath, 'utf-8');
|
|
1007
|
+
}
|
|
1008
|
+
catch (err) {
|
|
1009
|
+
return {
|
|
1010
|
+
filePath,
|
|
1011
|
+
language,
|
|
1012
|
+
nodes: [],
|
|
1013
|
+
edges: [],
|
|
1014
|
+
parseErrors: [`Failed to read file: ${err instanceof Error ? err.message : String(err)}`],
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
if (!content.trim()) {
|
|
1018
|
+
return {
|
|
1019
|
+
filePath,
|
|
1020
|
+
language,
|
|
1021
|
+
nodes: [],
|
|
1022
|
+
edges: [],
|
|
1023
|
+
parseErrors: [],
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
const parseErrors = [];
|
|
1027
|
+
let nodes = [];
|
|
1028
|
+
let edges = [];
|
|
1029
|
+
// Try tree-sitter first
|
|
1030
|
+
const ts = await getTreeSitterParser(language);
|
|
1031
|
+
if (ts) {
|
|
1032
|
+
try {
|
|
1033
|
+
ts.parser.setLanguage(ts.grammar);
|
|
1034
|
+
const tree = ts.parser.parse(content);
|
|
1035
|
+
if (tree.rootNode.hasError) {
|
|
1036
|
+
parseErrors.push('Tree-sitter reported parse errors; results may be incomplete');
|
|
1037
|
+
}
|
|
1038
|
+
const result = extractFromTreeSitter(tree, content, filePath, language);
|
|
1039
|
+
nodes = result.nodes;
|
|
1040
|
+
edges = result.edges;
|
|
1041
|
+
}
|
|
1042
|
+
catch (err) {
|
|
1043
|
+
parseErrors.push(`Tree-sitter parsing failed: ${err instanceof Error ? err.message : String(err)}, using regex fallback`);
|
|
1044
|
+
const result = parseWithRegex(content, filePath, language);
|
|
1045
|
+
nodes = result.nodes;
|
|
1046
|
+
edges = result.edges;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
else {
|
|
1050
|
+
// Use regex fallback
|
|
1051
|
+
const result = parseWithRegex(content, filePath, language);
|
|
1052
|
+
nodes = result.nodes;
|
|
1053
|
+
edges = result.edges;
|
|
1054
|
+
}
|
|
1055
|
+
return { filePath, language, nodes, edges, parseErrors };
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Parse multiple files concurrently in batches.
|
|
1059
|
+
*
|
|
1060
|
+
* @param files - Array of file paths to parse
|
|
1061
|
+
* @param concurrency - Max concurrent parses (default 8)
|
|
1062
|
+
* @param onProgress - Optional progress callback (current, total)
|
|
1063
|
+
* @returns Array of ParseResults
|
|
1064
|
+
*/
|
|
1065
|
+
export async function parseFiles(files, concurrency = 8, onProgress) {
|
|
1066
|
+
const results = [];
|
|
1067
|
+
const total = files.length;
|
|
1068
|
+
for (let i = 0; i < total; i += concurrency) {
|
|
1069
|
+
const batch = files.slice(i, i + concurrency);
|
|
1070
|
+
const batchResults = await Promise.all(batch.map(f => parseFile(f)));
|
|
1071
|
+
results.push(...batchResults);
|
|
1072
|
+
if (onProgress) {
|
|
1073
|
+
onProgress(Math.min(i + concurrency, total), total);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
return results;
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Check if a file extension is supported for parsing.
|
|
1080
|
+
*/
|
|
1081
|
+
export function isSupportedFile(filePath) {
|
|
1082
|
+
return detectLanguage(filePath) !== null;
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Get the list of supported languages.
|
|
1086
|
+
*/
|
|
1087
|
+
export function getSupportedLanguages() {
|
|
1088
|
+
return [...new Set(Object.values(EXTENSION_MAP))];
|
|
1089
|
+
}
|
|
1090
|
+
//# sourceMappingURL=parser.js.map
|