neuro-cli 4.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 +510 -0
- package/dist/advisor/advisor.d.ts +50 -0
- package/dist/advisor/advisor.js +178 -0
- package/dist/agents/base.d.ts +62 -0
- package/dist/agents/base.js +215 -0
- package/dist/agents/orchestrator.d.ts +46 -0
- package/dist/agents/orchestrator.js +192 -0
- package/dist/agents/team.d.ts +51 -0
- package/dist/agents/team.js +210 -0
- package/dist/api/models.d.ts +23 -0
- package/dist/api/models.js +514 -0
- package/dist/api/ollama.d.ts +153 -0
- package/dist/api/ollama.js +751 -0
- package/dist/api/openrouter.d.ts +55 -0
- package/dist/api/openrouter.js +223 -0
- package/dist/commands/commands.d.ts +43 -0
- package/dist/commands/commands.js +308 -0
- package/dist/config/config.d.ts +8 -0
- package/dist/config/config.js +311 -0
- package/dist/context/compaction.d.ts +54 -0
- package/dist/context/compaction.js +251 -0
- package/dist/context/custom-agents.d.ts +107 -0
- package/dist/context/custom-agents.js +397 -0
- package/dist/context/custom-tools.d.ts +120 -0
- package/dist/context/custom-tools.js +564 -0
- package/dist/context/git-checkpoint.d.ts +52 -0
- package/dist/context/git-checkpoint.js +240 -0
- package/dist/context/neuro-md.d.ts +48 -0
- package/dist/context/neuro-md.js +202 -0
- package/dist/context/neuroignore.d.ts +102 -0
- package/dist/context/neuroignore.js +441 -0
- package/dist/context/repo-map.d.ts +38 -0
- package/dist/context/repo-map.js +220 -0
- package/dist/context/skill-standard.d.ts +262 -0
- package/dist/context/skill-standard.js +1156 -0
- package/dist/context/skill-system.d.ts +75 -0
- package/dist/context/skill-system.js +578 -0
- package/dist/context/tree-sitter.d.ts +99 -0
- package/dist/context/tree-sitter.js +1956 -0
- package/dist/core/acp.d.ts +325 -0
- package/dist/core/acp.js +1498 -0
- package/dist/core/api-server.d.ts +143 -0
- package/dist/core/api-server.js +550 -0
- package/dist/core/approval.d.ts +81 -0
- package/dist/core/approval.js +432 -0
- package/dist/core/auto-compact.d.ts +127 -0
- package/dist/core/auto-compact.js +436 -0
- package/dist/core/auto-mode.d.ts +232 -0
- package/dist/core/auto-mode.js +831 -0
- package/dist/core/background-session.d.ts +166 -0
- package/dist/core/background-session.js +696 -0
- package/dist/core/cicd.d.ts +257 -0
- package/dist/core/cicd.js +1443 -0
- package/dist/core/cloud-sync.d.ts +156 -0
- package/dist/core/cloud-sync.js +582 -0
- package/dist/core/code-review.d.ts +132 -0
- package/dist/core/code-review.js +1191 -0
- package/dist/core/completion.d.ts +49 -0
- package/dist/core/completion.js +384 -0
- package/dist/core/context.d.ts +38 -0
- package/dist/core/context.js +144 -0
- package/dist/core/diff-preview.d.ts +35 -0
- package/dist/core/diff-preview.js +173 -0
- package/dist/core/doom-loop.d.ts +51 -0
- package/dist/core/doom-loop.js +179 -0
- package/dist/core/engine.d.ts +183 -0
- package/dist/core/engine.js +942 -0
- package/dist/core/extended-thinking.d.ts +103 -0
- package/dist/core/extended-thinking.js +269 -0
- package/dist/core/fallback.d.ts +54 -0
- package/dist/core/fallback.js +104 -0
- package/dist/core/git-worktree.d.ts +139 -0
- package/dist/core/git-worktree.js +614 -0
- package/dist/core/headless.d.ts +30 -0
- package/dist/core/headless.js +101 -0
- package/dist/core/i18n.d.ts +83 -0
- package/dist/core/i18n.js +739 -0
- package/dist/core/linting.d.ts +129 -0
- package/dist/core/linting.js +699 -0
- package/dist/core/model-router.d.ts +109 -0
- package/dist/core/model-router.js +581 -0
- package/dist/core/multi-model.d.ts +243 -0
- package/dist/core/multi-model.js +1099 -0
- package/dist/core/multi-session.d.ts +144 -0
- package/dist/core/multi-session.js +442 -0
- package/dist/core/multimodal.d.ts +125 -0
- package/dist/core/multimodal.js +286 -0
- package/dist/core/observability.d.ts +93 -0
- package/dist/core/observability.js +737 -0
- package/dist/core/os-sandbox.d.ts +122 -0
- package/dist/core/os-sandbox.js +1193 -0
- package/dist/core/outcome-grading.d.ts +228 -0
- package/dist/core/outcome-grading.js +1123 -0
- package/dist/core/output-styles.d.ts +57 -0
- package/dist/core/output-styles.js +382 -0
- package/dist/core/parallel-agents.d.ts +183 -0
- package/dist/core/parallel-agents.js +563 -0
- package/dist/core/plugin-bundle.d.ts +236 -0
- package/dist/core/plugin-bundle.js +887 -0
- package/dist/core/plugin-sdk.d.ts +139 -0
- package/dist/core/plugin-sdk.js +273 -0
- package/dist/core/prompt-cache.d.ts +163 -0
- package/dist/core/prompt-cache.js +599 -0
- package/dist/core/sandbox.d.ts +127 -0
- package/dist/core/sandbox.js +369 -0
- package/dist/core/scheduled-tasks.d.ts +151 -0
- package/dist/core/scheduled-tasks.js +502 -0
- package/dist/core/security-scanner.d.ts +160 -0
- package/dist/core/security-scanner.js +1494 -0
- package/dist/core/session.d.ts +83 -0
- package/dist/core/session.js +269 -0
- package/dist/core/shell-completion.d.ts +51 -0
- package/dist/core/shell-completion.js +674 -0
- package/dist/core/smart-monitor.d.ts +146 -0
- package/dist/core/smart-monitor.js +1199 -0
- package/dist/core/spec-driven.d.ts +233 -0
- package/dist/core/spec-driven.js +1485 -0
- package/dist/core/spending-warnings.d.ts +123 -0
- package/dist/core/spending-warnings.js +456 -0
- package/dist/core/sub-agent.d.ts +298 -0
- package/dist/core/sub-agent.js +1023 -0
- package/dist/core/telemetry.d.ts +157 -0
- package/dist/core/telemetry.js +412 -0
- package/dist/core/terminal-ux.d.ts +134 -0
- package/dist/core/terminal-ux.js +649 -0
- package/dist/core/testing.d.ts +146 -0
- package/dist/core/testing.js +1200 -0
- package/dist/core/types.d.ts +439 -0
- package/dist/core/types.js +6 -0
- package/dist/core/undo-redo.d.ts +112 -0
- package/dist/core/undo-redo.js +290 -0
- package/dist/core/updater.d.ts +159 -0
- package/dist/core/updater.js +608 -0
- package/dist/core/vim-mode.d.ts +151 -0
- package/dist/core/vim-mode.js +771 -0
- package/dist/core/voice.d.ts +137 -0
- package/dist/core/voice.js +538 -0
- package/dist/core/web-dashboard.d.ts +109 -0
- package/dist/core/web-dashboard.js +484 -0
- package/dist/hooks/hooks.d.ts +74 -0
- package/dist/hooks/hooks.js +160 -0
- package/dist/hooks/llm-evaluator.d.ts +165 -0
- package/dist/hooks/llm-evaluator.js +560 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1186 -0
- package/dist/lsp/lsp-manager.d.ts +63 -0
- package/dist/lsp/lsp-manager.js +351 -0
- package/dist/mcp/client.d.ts +133 -0
- package/dist/mcp/client.js +684 -0
- package/dist/mcp/mcp-apps.d.ts +70 -0
- package/dist/mcp/mcp-apps.js +1007 -0
- package/dist/tools/bash.d.ts +5 -0
- package/dist/tools/bash.js +195 -0
- package/dist/tools/browser.d.ts +92 -0
- package/dist/tools/browser.js +1570 -0
- package/dist/tools/extended.d.ts +6 -0
- package/dist/tools/extended.js +191 -0
- package/dist/tools/file.d.ts +10 -0
- package/dist/tools/file.js +382 -0
- package/dist/tools/github.d.ts +389 -0
- package/dist/tools/github.js +759 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +40 -0
- package/dist/tools/memory.d.ts +6 -0
- package/dist/tools/memory.js +197 -0
- package/dist/tools/registry.d.ts +29 -0
- package/dist/tools/registry.js +64 -0
- package/dist/tools/web.d.ts +6 -0
- package/dist/tools/web.js +150 -0
- package/dist/ui/renderer.d.ts +97 -0
- package/dist/ui/renderer.js +279 -0
- package/dist/ui/theme.d.ts +27 -0
- package/dist/ui/theme.js +106 -0
- package/package.json +83 -0
|
@@ -0,0 +1,1956 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// NeuroCLI - Tree-sitter Integration
|
|
3
|
+
// Regex-based AST parser for repository mapping
|
|
4
|
+
// Inspired by Aider's lightweight "good enough" approach
|
|
5
|
+
// No external dependencies - uses only Node.js built-ins
|
|
6
|
+
// ============================================================
|
|
7
|
+
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
8
|
+
import { join, extname, relative, basename, dirname } from 'path';
|
|
9
|
+
import { execSync } from 'child_process';
|
|
10
|
+
// ============================================================
|
|
11
|
+
// Language Configuration
|
|
12
|
+
// ============================================================
|
|
13
|
+
const LANGUAGE_EXTENSIONS = {
|
|
14
|
+
'.ts': 'typescript',
|
|
15
|
+
'.tsx': 'typescript',
|
|
16
|
+
'.js': 'javascript',
|
|
17
|
+
'.jsx': 'javascript',
|
|
18
|
+
'.mjs': 'javascript',
|
|
19
|
+
'.cjs': 'javascript',
|
|
20
|
+
'.py': 'python',
|
|
21
|
+
'.pyi': 'python',
|
|
22
|
+
'.pyx': 'python',
|
|
23
|
+
'.go': 'go',
|
|
24
|
+
'.rs': 'rust',
|
|
25
|
+
'.java': 'java',
|
|
26
|
+
'.kt': 'kotlin',
|
|
27
|
+
'.css': 'css',
|
|
28
|
+
'.scss': 'css',
|
|
29
|
+
'.less': 'css',
|
|
30
|
+
'.html': 'html',
|
|
31
|
+
'.htm': 'html',
|
|
32
|
+
'.vue': 'vue',
|
|
33
|
+
'.svelte': 'svelte',
|
|
34
|
+
};
|
|
35
|
+
const LANGUAGE_NAMES = {
|
|
36
|
+
'typescript': 'TypeScript',
|
|
37
|
+
'javascript': 'JavaScript',
|
|
38
|
+
'python': 'Python',
|
|
39
|
+
'go': 'Go',
|
|
40
|
+
'rust': 'Rust',
|
|
41
|
+
'java': 'Java',
|
|
42
|
+
'kotlin': 'Kotlin',
|
|
43
|
+
'css': 'CSS',
|
|
44
|
+
'html': 'HTML',
|
|
45
|
+
'vue': 'Vue',
|
|
46
|
+
'svelte': 'Svelte',
|
|
47
|
+
};
|
|
48
|
+
const SKIP_DIRS = new Set([
|
|
49
|
+
'node_modules', '.git', '__pycache__', '.next', 'dist', '.turbo', '.cache',
|
|
50
|
+
'build', 'out', 'target', '.cargo', 'vendor', '.venv', 'venv', 'env',
|
|
51
|
+
'.tox', '.mypy_cache', '.pytest_cache', '.hg', '.svn', 'coverage',
|
|
52
|
+
]);
|
|
53
|
+
const BINARY_EXTENSIONS = new Set([
|
|
54
|
+
'.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.ico', '.bmp',
|
|
55
|
+
'.mp3', '.mp4', '.wav', '.avi', '.mov', '.mkv', '.flac',
|
|
56
|
+
'.zip', '.tar', '.gz', '.bz2', '.xz', '.7z', '.rar',
|
|
57
|
+
'.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
|
|
58
|
+
'.woff', '.woff2', '.ttf', '.eot', '.otf',
|
|
59
|
+
'.wasm', '.so', '.dll', '.dylib', '.exe',
|
|
60
|
+
]);
|
|
61
|
+
// ---- TypeScript / JavaScript Parser ----
|
|
62
|
+
function parseTypeScript(content) {
|
|
63
|
+
const lines = content.split('\n');
|
|
64
|
+
const symbols = [];
|
|
65
|
+
const imports = [];
|
|
66
|
+
const exports = [];
|
|
67
|
+
for (let i = 0; i < lines.length; i++) {
|
|
68
|
+
const line = lines[i];
|
|
69
|
+
const lineNum = i + 1;
|
|
70
|
+
// ---- Imports ----
|
|
71
|
+
const importMatch = line.match(/^import\s+(?:type\s+)?(?:(\{[^}]*\})|(\*\s+as\s+\w+)|(\w+))\s+from\s+['"]([^'"]+)['"]/);
|
|
72
|
+
if (importMatch) {
|
|
73
|
+
const [, namedImports, namespaceImport, defaultImport, source] = importMatch;
|
|
74
|
+
const items = [];
|
|
75
|
+
if (namedImports) {
|
|
76
|
+
items.push(...namedImports.replace(/[{}]/g, '').split(',').map(s => s.trim().split(/\s+as\s+/)[0].trim()).filter(Boolean));
|
|
77
|
+
}
|
|
78
|
+
if (namespaceImport) {
|
|
79
|
+
items.push(namespaceImport.replace(/\*\s+as\s+/, '').trim());
|
|
80
|
+
}
|
|
81
|
+
if (defaultImport) {
|
|
82
|
+
items.push(defaultImport.trim());
|
|
83
|
+
}
|
|
84
|
+
imports.push({
|
|
85
|
+
source,
|
|
86
|
+
items,
|
|
87
|
+
line: lineNum,
|
|
88
|
+
isTypeOnly: line.includes('import type'),
|
|
89
|
+
});
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
// Side-effect import
|
|
93
|
+
const sideEffectImport = line.match(/^import\s+['"]([^'"]+)['"]/);
|
|
94
|
+
if (sideEffectImport) {
|
|
95
|
+
imports.push({
|
|
96
|
+
source: sideEffectImport[1],
|
|
97
|
+
items: [],
|
|
98
|
+
line: lineNum,
|
|
99
|
+
isTypeOnly: false,
|
|
100
|
+
});
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
// Require import
|
|
104
|
+
const requireMatch = line.match(/(?:const|let|var)\s+(?:\{([^}]*)\}|(\w+))\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
|
|
105
|
+
if (requireMatch) {
|
|
106
|
+
const [, namedImports, defaultImport, source] = requireMatch;
|
|
107
|
+
const items = [];
|
|
108
|
+
if (namedImports) {
|
|
109
|
+
items.push(...namedImports.split(',').map(s => s.trim().split(/\s*:\s*/)[0].trim()).filter(Boolean));
|
|
110
|
+
}
|
|
111
|
+
if (defaultImport) {
|
|
112
|
+
items.push(defaultImport.trim());
|
|
113
|
+
}
|
|
114
|
+
imports.push({ source, items, line: lineNum, isTypeOnly: false });
|
|
115
|
+
}
|
|
116
|
+
// ---- Exports ----
|
|
117
|
+
const exportDefaultMatch = line.match(/^export\s+default\s+(?:function\s+)?(\w+)/);
|
|
118
|
+
if (exportDefaultMatch) {
|
|
119
|
+
exports.push({
|
|
120
|
+
name: exportDefaultMatch[1],
|
|
121
|
+
kind: 'function',
|
|
122
|
+
line: lineNum,
|
|
123
|
+
isDefault: true,
|
|
124
|
+
isReexport: false,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
const reexportMatch = line.match(/^export\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/);
|
|
128
|
+
if (reexportMatch) {
|
|
129
|
+
const items = reexportMatch[1].split(',').map(s => {
|
|
130
|
+
const parts = s.trim().split(/\s+as\s+/);
|
|
131
|
+
return parts[parts.length - 1].trim();
|
|
132
|
+
});
|
|
133
|
+
for (const item of items) {
|
|
134
|
+
exports.push({
|
|
135
|
+
name: item,
|
|
136
|
+
kind: 'reexport',
|
|
137
|
+
line: lineNum,
|
|
138
|
+
isDefault: false,
|
|
139
|
+
isReexport: true,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
// ---- Classes ----
|
|
145
|
+
const classMatch = line.match(/^(export\s+)?(default\s+)?(declare\s+)?(abstract\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([^{]+))?/);
|
|
146
|
+
if (classMatch) {
|
|
147
|
+
const [, exportKw, defaultKw, , abstractKw, name, extendsCls, implementsIf] = classMatch;
|
|
148
|
+
const modifiers = [];
|
|
149
|
+
if (exportKw)
|
|
150
|
+
modifiers.push('export');
|
|
151
|
+
if (defaultKw)
|
|
152
|
+
modifiers.push('default');
|
|
153
|
+
if (abstractKw)
|
|
154
|
+
modifiers.push('abstract');
|
|
155
|
+
const endLine = findBlockEnd(lines, i);
|
|
156
|
+
const signature = line.trim().replace(/\{$/, '').trim();
|
|
157
|
+
const symbol = {
|
|
158
|
+
name,
|
|
159
|
+
kind: 'class',
|
|
160
|
+
line: lineNum,
|
|
161
|
+
endLine,
|
|
162
|
+
signature,
|
|
163
|
+
modifiers,
|
|
164
|
+
children: [],
|
|
165
|
+
};
|
|
166
|
+
// Parse class members (methods, properties)
|
|
167
|
+
const classBody = lines.slice(i, endLine);
|
|
168
|
+
for (let j = 0; j < classBody.length; j++) {
|
|
169
|
+
const memberLine = classBody[j];
|
|
170
|
+
const methodMatch = memberLine.match(/^\s+(?:(?:public|private|protected|static|abstract|readonly|async|override|declare)\s+)*(?:(?:get|set)\s+)?(\w+)\s*[<(]/);
|
|
171
|
+
if (methodMatch) {
|
|
172
|
+
const methodModifiers = extractModifiers(memberLine);
|
|
173
|
+
const methodName = methodMatch[1];
|
|
174
|
+
// Skip constructor - it's implicit
|
|
175
|
+
if (methodName === 'constructor') {
|
|
176
|
+
symbol.children.push({
|
|
177
|
+
name: 'constructor',
|
|
178
|
+
kind: 'method',
|
|
179
|
+
line: lineNum + j,
|
|
180
|
+
endLine: findBlockEnd(classBody, j) + lineNum,
|
|
181
|
+
signature: memberLine.trim().replace(/\{$/, '').trim(),
|
|
182
|
+
modifiers: methodModifiers,
|
|
183
|
+
});
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
// Determine if it's a getter/setter or regular method
|
|
187
|
+
const isAccessor = /^\s+(?:public|private|protected|static)\s+(?:get|set)\s+/.test(memberLine);
|
|
188
|
+
symbol.children.push({
|
|
189
|
+
name: methodName,
|
|
190
|
+
kind: isAccessor ? 'method' : 'method',
|
|
191
|
+
line: lineNum + j,
|
|
192
|
+
endLine: findBlockEnd(classBody, j) + lineNum,
|
|
193
|
+
signature: memberLine.trim().replace(/\{$/, '').trim(),
|
|
194
|
+
modifiers: methodModifiers,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
// Class properties
|
|
198
|
+
const propMatch = memberLine.match(/^\s+(?:(?:public|private|protected|static|abstract|readonly|declare)\s+)+(\w+)\s*[?:!]/);
|
|
199
|
+
if (propMatch && !memberLine.includes('(')) {
|
|
200
|
+
symbol.children.push({
|
|
201
|
+
name: propMatch[1],
|
|
202
|
+
kind: 'variable',
|
|
203
|
+
line: lineNum + j,
|
|
204
|
+
endLine: lineNum + j,
|
|
205
|
+
modifiers: extractModifiers(memberLine),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
symbols.push(symbol);
|
|
210
|
+
if (exportKw) {
|
|
211
|
+
exports.push({
|
|
212
|
+
name,
|
|
213
|
+
kind: 'class',
|
|
214
|
+
line: lineNum,
|
|
215
|
+
isDefault: !!defaultKw,
|
|
216
|
+
isReexport: false,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
// ---- Interfaces ----
|
|
222
|
+
const interfaceMatch = line.match(/^(export\s+)?(declare\s+)?interface\s+(\w+)(?:\s+extends\s+([^{]+))?/);
|
|
223
|
+
if (interfaceMatch) {
|
|
224
|
+
const [, exportKw, , name, extendsIf] = interfaceMatch;
|
|
225
|
+
const modifiers = [];
|
|
226
|
+
if (exportKw)
|
|
227
|
+
modifiers.push('export');
|
|
228
|
+
const endLine = findBlockEnd(lines, i);
|
|
229
|
+
const signature = line.trim().replace(/\{$/, '').trim();
|
|
230
|
+
const symbol = {
|
|
231
|
+
name,
|
|
232
|
+
kind: 'interface',
|
|
233
|
+
line: lineNum,
|
|
234
|
+
endLine,
|
|
235
|
+
signature,
|
|
236
|
+
modifiers,
|
|
237
|
+
children: [],
|
|
238
|
+
};
|
|
239
|
+
// Parse interface members
|
|
240
|
+
const ifaceBody = lines.slice(i, endLine);
|
|
241
|
+
for (let j = 0; j < ifaceBody.length; j++) {
|
|
242
|
+
const memberLine = ifaceBody[j];
|
|
243
|
+
const methodMatch = memberLine.match(/^\s+(\w+)\s*[<(]/);
|
|
244
|
+
if (methodMatch) {
|
|
245
|
+
symbol.children.push({
|
|
246
|
+
name: methodMatch[1],
|
|
247
|
+
kind: 'method',
|
|
248
|
+
line: lineNum + j,
|
|
249
|
+
endLine: lineNum + j,
|
|
250
|
+
signature: memberLine.trim().replace(/[;,{]$/, '').trim(),
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
const propMatch = memberLine.match(/^\s+(\w+)\s*[?!:]/);
|
|
254
|
+
if (propMatch && !memberLine.includes('(')) {
|
|
255
|
+
symbol.children.push({
|
|
256
|
+
name: propMatch[1],
|
|
257
|
+
kind: 'variable',
|
|
258
|
+
line: lineNum + j,
|
|
259
|
+
endLine: lineNum + j,
|
|
260
|
+
signature: memberLine.trim().replace(/[;,{]$/, '').trim(),
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
symbols.push(symbol);
|
|
265
|
+
if (exportKw) {
|
|
266
|
+
exports.push({
|
|
267
|
+
name,
|
|
268
|
+
kind: 'interface',
|
|
269
|
+
line: lineNum,
|
|
270
|
+
isDefault: false,
|
|
271
|
+
isReexport: false,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
// ---- Type Aliases ----
|
|
277
|
+
const typeMatch = line.match(/^(export\s+)?(declare\s+)?type\s+(\w+)\s*(?:<[^>]+>)?\s*=/);
|
|
278
|
+
if (typeMatch) {
|
|
279
|
+
const [, exportKw, , name] = typeMatch;
|
|
280
|
+
const endLine = findTypeEnd(lines, i);
|
|
281
|
+
symbols.push({
|
|
282
|
+
name,
|
|
283
|
+
kind: 'type',
|
|
284
|
+
line: lineNum,
|
|
285
|
+
endLine,
|
|
286
|
+
signature: line.trim().slice(0, 120),
|
|
287
|
+
modifiers: exportKw ? ['export'] : [],
|
|
288
|
+
});
|
|
289
|
+
if (exportKw) {
|
|
290
|
+
exports.push({
|
|
291
|
+
name,
|
|
292
|
+
kind: 'type',
|
|
293
|
+
line: lineNum,
|
|
294
|
+
isDefault: false,
|
|
295
|
+
isReexport: false,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
// ---- Enums ----
|
|
301
|
+
const enumMatch = line.match(/^(export\s+)?(declare\s+)?(?:const\s+)?enum\s+(\w+)/);
|
|
302
|
+
if (enumMatch) {
|
|
303
|
+
const [, exportKw, , name] = enumMatch;
|
|
304
|
+
const endLine = findBlockEnd(lines, i);
|
|
305
|
+
symbols.push({
|
|
306
|
+
name,
|
|
307
|
+
kind: 'enum',
|
|
308
|
+
line: lineNum,
|
|
309
|
+
endLine,
|
|
310
|
+
signature: line.trim().replace(/\{$/, '').trim(),
|
|
311
|
+
modifiers: exportKw ? ['export'] : [],
|
|
312
|
+
});
|
|
313
|
+
if (exportKw) {
|
|
314
|
+
exports.push({
|
|
315
|
+
name,
|
|
316
|
+
kind: 'enum',
|
|
317
|
+
line: lineNum,
|
|
318
|
+
isDefault: false,
|
|
319
|
+
isReexport: false,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
// ---- Functions ----
|
|
325
|
+
const funcMatch = line.match(/^(export\s+)?(default\s+)?(declare\s+)?(async\s+)?function\s+(\w+)\s*([<(])/);
|
|
326
|
+
if (funcMatch) {
|
|
327
|
+
const [, exportKw, defaultKw, , asyncKw, name] = funcMatch;
|
|
328
|
+
const modifiers = [];
|
|
329
|
+
if (exportKw)
|
|
330
|
+
modifiers.push('export');
|
|
331
|
+
if (defaultKw)
|
|
332
|
+
modifiers.push('default');
|
|
333
|
+
if (asyncKw)
|
|
334
|
+
modifiers.push('async');
|
|
335
|
+
const endLine = findBlockEnd(lines, i);
|
|
336
|
+
const signature = line.trim().replace(/\{$/, '').trim();
|
|
337
|
+
symbols.push({
|
|
338
|
+
name,
|
|
339
|
+
kind: 'function',
|
|
340
|
+
line: lineNum,
|
|
341
|
+
endLine,
|
|
342
|
+
signature: signature.slice(0, 200),
|
|
343
|
+
modifiers,
|
|
344
|
+
});
|
|
345
|
+
if (exportKw) {
|
|
346
|
+
exports.push({
|
|
347
|
+
name,
|
|
348
|
+
kind: 'function',
|
|
349
|
+
line: lineNum,
|
|
350
|
+
isDefault: !!defaultKw,
|
|
351
|
+
isReexport: false,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
// ---- Arrow Functions / Const Declarations ----
|
|
357
|
+
const constArrowMatch = line.match(/^(export\s+)?(declare\s+)?const\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>/);
|
|
358
|
+
if (constArrowMatch) {
|
|
359
|
+
const [, exportKw, , name] = constArrowMatch;
|
|
360
|
+
const modifiers = [];
|
|
361
|
+
if (exportKw)
|
|
362
|
+
modifiers.push('export');
|
|
363
|
+
symbols.push({
|
|
364
|
+
name,
|
|
365
|
+
kind: 'function',
|
|
366
|
+
line: lineNum,
|
|
367
|
+
endLine: findStatementEnd(lines, i),
|
|
368
|
+
signature: line.trim().slice(0, 200),
|
|
369
|
+
modifiers,
|
|
370
|
+
});
|
|
371
|
+
if (exportKw) {
|
|
372
|
+
exports.push({
|
|
373
|
+
name,
|
|
374
|
+
kind: 'function',
|
|
375
|
+
line: lineNum,
|
|
376
|
+
isDefault: false,
|
|
377
|
+
isReexport: false,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
// ---- Regular Const/Let/Var ----
|
|
383
|
+
const varMatch = line.match(/^(export\s+)?(declare\s+)?const\s+(\w+)\s*(?::\s*[^=]+)?\s*=/);
|
|
384
|
+
if (varMatch) {
|
|
385
|
+
const [, exportKw, , name] = varMatch;
|
|
386
|
+
// Skip if it's an arrow function (already handled)
|
|
387
|
+
if (!line.includes('=>')) {
|
|
388
|
+
symbols.push({
|
|
389
|
+
name,
|
|
390
|
+
kind: 'constant',
|
|
391
|
+
line: lineNum,
|
|
392
|
+
endLine: lineNum,
|
|
393
|
+
signature: line.trim().slice(0, 120),
|
|
394
|
+
modifiers: exportKw ? ['export'] : [],
|
|
395
|
+
});
|
|
396
|
+
if (exportKw) {
|
|
397
|
+
exports.push({
|
|
398
|
+
name,
|
|
399
|
+
kind: 'constant',
|
|
400
|
+
line: lineNum,
|
|
401
|
+
isDefault: false,
|
|
402
|
+
isReexport: false,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
const letMatch = line.match(/^(export\s+)?let\s+(\w+)\s*(?::\s*[^=]+)?\s*=/);
|
|
409
|
+
if (letMatch) {
|
|
410
|
+
const [, exportKw, name] = letMatch;
|
|
411
|
+
symbols.push({
|
|
412
|
+
name,
|
|
413
|
+
kind: 'variable',
|
|
414
|
+
line: lineNum,
|
|
415
|
+
endLine: lineNum,
|
|
416
|
+
modifiers: exportKw ? ['export'] : [],
|
|
417
|
+
});
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
// ---- Namespaces ----
|
|
421
|
+
const namespaceMatch = line.match(/^(export\s+)?namespace\s+(\w+)/);
|
|
422
|
+
if (namespaceMatch) {
|
|
423
|
+
const [, exportKw, name] = namespaceMatch;
|
|
424
|
+
const endLine = findBlockEnd(lines, i);
|
|
425
|
+
symbols.push({
|
|
426
|
+
name,
|
|
427
|
+
kind: 'namespace',
|
|
428
|
+
line: lineNum,
|
|
429
|
+
endLine,
|
|
430
|
+
signature: line.trim().replace(/\{$/, '').trim(),
|
|
431
|
+
modifiers: exportKw ? ['export'] : [],
|
|
432
|
+
});
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return { symbols, imports, exports };
|
|
437
|
+
}
|
|
438
|
+
// ---- Python Parser ----
|
|
439
|
+
function parsePython(content) {
|
|
440
|
+
const lines = content.split('\n');
|
|
441
|
+
const symbols = [];
|
|
442
|
+
const imports = [];
|
|
443
|
+
const exports = [];
|
|
444
|
+
for (let i = 0; i < lines.length; i++) {
|
|
445
|
+
const line = lines[i];
|
|
446
|
+
const lineNum = i + 1;
|
|
447
|
+
const indent = line.match(/^(\s*)/)?.[1].length || 0;
|
|
448
|
+
// Skip comments and empty lines
|
|
449
|
+
if (line.trim().startsWith('#') || line.trim() === '')
|
|
450
|
+
continue;
|
|
451
|
+
// ---- Imports ----
|
|
452
|
+
const importMatch = line.match(/^(\s*)import\s+([^\n]+)/);
|
|
453
|
+
if (importMatch) {
|
|
454
|
+
const modules = importMatch[2].split(',').map(s => s.trim().split(/\s+as\s+/)[0].trim());
|
|
455
|
+
for (const mod of modules) {
|
|
456
|
+
imports.push({ source: mod, items: [], line: lineNum, isTypeOnly: false });
|
|
457
|
+
}
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
const fromImportMatch = line.match(/^(\s*)from\s+([^\s]+)\s+import\s+([^\n]+)/);
|
|
461
|
+
if (fromImportMatch) {
|
|
462
|
+
const source = fromImportMatch[2];
|
|
463
|
+
const items = fromImportMatch[3].split(',').map(s => {
|
|
464
|
+
const parts = s.trim().split(/\s+as\s+/);
|
|
465
|
+
return parts[0].trim();
|
|
466
|
+
}).filter(Boolean);
|
|
467
|
+
imports.push({ source, items, line: lineNum, isTypeOnly: false });
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
// Only process top-level symbols (indent === 0)
|
|
471
|
+
if (indent > 0)
|
|
472
|
+
continue;
|
|
473
|
+
// ---- Decorators ----
|
|
474
|
+
const decorators = [];
|
|
475
|
+
let j = i;
|
|
476
|
+
while (j < lines.length && lines[j].trimStart().startsWith('@')) {
|
|
477
|
+
const decMatch = lines[j].trim().match(/@(\w+)/);
|
|
478
|
+
if (decMatch)
|
|
479
|
+
decorators.push(decMatch[1]);
|
|
480
|
+
j++;
|
|
481
|
+
}
|
|
482
|
+
// Adjust i if we consumed decorators
|
|
483
|
+
if (j > i) {
|
|
484
|
+
i = j - 1;
|
|
485
|
+
continue; // The actual def/class will be on next iteration
|
|
486
|
+
}
|
|
487
|
+
// ---- Classes ----
|
|
488
|
+
const classMatch = line.match(/^class\s+(\w+)(?:\(([^)]*)\))?\s*:/);
|
|
489
|
+
if (classMatch) {
|
|
490
|
+
const name = classMatch[1];
|
|
491
|
+
const baseClasses = classMatch[2]?.split(',').map(s => s.trim()).filter(Boolean) || [];
|
|
492
|
+
const endLine = findPythonBlockEnd(lines, i);
|
|
493
|
+
// Collect decorators from lines above
|
|
494
|
+
const classDecorators = [];
|
|
495
|
+
for (let k = i - 1; k >= 0; k--) {
|
|
496
|
+
if (lines[k].trimStart().startsWith('@')) {
|
|
497
|
+
const dMatch = lines[k].trim().match(/@(\w+)/);
|
|
498
|
+
if (dMatch)
|
|
499
|
+
classDecorators.unshift(dMatch[1]);
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const symbol = {
|
|
506
|
+
name,
|
|
507
|
+
kind: 'class',
|
|
508
|
+
line: lineNum,
|
|
509
|
+
endLine,
|
|
510
|
+
signature: line.trim().replace(/:\s*$/, ''),
|
|
511
|
+
modifiers: classDecorators.length > 0 ? classDecorators : undefined,
|
|
512
|
+
children: [],
|
|
513
|
+
};
|
|
514
|
+
// Parse class methods
|
|
515
|
+
const classBody = lines.slice(i + 1, endLine);
|
|
516
|
+
let methodIndent = -1;
|
|
517
|
+
for (let k = 0; k < classBody.length; k++) {
|
|
518
|
+
const mLine = classBody[k];
|
|
519
|
+
const mIndent = mLine.match(/^(\s*)/)?.[1].length || 0;
|
|
520
|
+
if (methodIndent === -1 && mLine.trim().startsWith('def ')) {
|
|
521
|
+
methodIndent = mIndent;
|
|
522
|
+
}
|
|
523
|
+
if (mIndent === methodIndent && mLine.trim().startsWith('def ')) {
|
|
524
|
+
const methodMatch = mLine.match(/^\s+(?:(?:async|staticmethod|classmethod)\s+)*def\s+(\w+)\s*\(([^)]*)\)/);
|
|
525
|
+
if (methodMatch) {
|
|
526
|
+
const methodName = methodMatch[1];
|
|
527
|
+
const methodModifiers = [];
|
|
528
|
+
// Check decorators above method
|
|
529
|
+
for (let d = k - 1; d >= 0; d--) {
|
|
530
|
+
if (classBody[d].trimStart().startsWith('@')) {
|
|
531
|
+
const dMatch = classBody[d].trim().match(/@(\w+)/);
|
|
532
|
+
if (dMatch)
|
|
533
|
+
methodModifiers.unshift(dMatch[1]);
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
if (mLine.includes('async '))
|
|
540
|
+
methodModifiers.push('async');
|
|
541
|
+
symbol.children.push({
|
|
542
|
+
name: methodName,
|
|
543
|
+
kind: 'method',
|
|
544
|
+
line: lineNum + k + 1,
|
|
545
|
+
endLine: findPythonBlockEnd(lines, i + 1 + k),
|
|
546
|
+
signature: mLine.trim().replace(/:\s*$/, ''),
|
|
547
|
+
modifiers: methodModifiers.length > 0 ? methodModifiers : undefined,
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
symbols.push(symbol);
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
// ---- Functions ----
|
|
556
|
+
const funcMatch = line.match(/^(?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)/);
|
|
557
|
+
if (funcMatch) {
|
|
558
|
+
const name = funcMatch[1];
|
|
559
|
+
const isAsync = line.includes('async ');
|
|
560
|
+
const endLine = findPythonBlockEnd(lines, i);
|
|
561
|
+
// Collect decorators
|
|
562
|
+
const funcDecorators = [];
|
|
563
|
+
for (let k = i - 1; k >= 0; k--) {
|
|
564
|
+
if (lines[k].trimStart().startsWith('@')) {
|
|
565
|
+
const dMatch = lines[k].trim().match(/@(\w+)/);
|
|
566
|
+
if (dMatch)
|
|
567
|
+
funcDecorators.unshift(dMatch[1]);
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
const modifiers = [];
|
|
574
|
+
if (isAsync)
|
|
575
|
+
modifiers.push('async');
|
|
576
|
+
if (funcDecorators.length > 0)
|
|
577
|
+
modifiers.push(...funcDecorators);
|
|
578
|
+
symbols.push({
|
|
579
|
+
name,
|
|
580
|
+
kind: 'function',
|
|
581
|
+
line: lineNum,
|
|
582
|
+
endLine,
|
|
583
|
+
signature: line.trim().replace(/:\s*$/, ''),
|
|
584
|
+
modifiers: modifiers.length > 0 ? modifiers : undefined,
|
|
585
|
+
});
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
// ---- Top-level Variables ----
|
|
589
|
+
const varMatch = line.match(/^([A-Z_][A-Z_0-9]*)\s*=/);
|
|
590
|
+
if (varMatch) {
|
|
591
|
+
symbols.push({
|
|
592
|
+
name: varMatch[1],
|
|
593
|
+
kind: 'constant',
|
|
594
|
+
line: lineNum,
|
|
595
|
+
endLine: lineNum,
|
|
596
|
+
});
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
const pyVarMatch = line.match(/^(\w+)\s*[:=]\s*(?:(?:async\s+)?def|class)\b/);
|
|
600
|
+
if (!pyVarMatch) {
|
|
601
|
+
const simpleVarMatch = line.match(/^(\w+)\s*=\s*/);
|
|
602
|
+
if (simpleVarMatch && !line.trim().startsWith('_')) {
|
|
603
|
+
symbols.push({
|
|
604
|
+
name: simpleVarMatch[1],
|
|
605
|
+
kind: 'variable',
|
|
606
|
+
line: lineNum,
|
|
607
|
+
endLine: lineNum,
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
// Python doesn't have explicit exports - use __all__ if present
|
|
613
|
+
const allMatch = content.match(/__all__\s*=\s*\[([^\]]+)\]/);
|
|
614
|
+
if (allMatch) {
|
|
615
|
+
const items = allMatch[1].split(',').map(s => s.trim().replace(/['"]/g, '')).filter(Boolean);
|
|
616
|
+
for (const item of items) {
|
|
617
|
+
exports.push({
|
|
618
|
+
name: item,
|
|
619
|
+
kind: 'unknown',
|
|
620
|
+
line: content.substring(0, content.indexOf('__all__')).split('\n').length,
|
|
621
|
+
isDefault: false,
|
|
622
|
+
isReexport: false,
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
return { symbols, imports, exports };
|
|
627
|
+
}
|
|
628
|
+
// ---- Go Parser ----
|
|
629
|
+
function parseGo(content) {
|
|
630
|
+
const lines = content.split('\n');
|
|
631
|
+
const symbols = [];
|
|
632
|
+
const imports = [];
|
|
633
|
+
const exports = [];
|
|
634
|
+
for (let i = 0; i < lines.length; i++) {
|
|
635
|
+
const line = lines[i];
|
|
636
|
+
const lineNum = i + 1;
|
|
637
|
+
// ---- Package ----
|
|
638
|
+
const pkgMatch = line.match(/^package\s+(\w+)/);
|
|
639
|
+
if (pkgMatch) {
|
|
640
|
+
symbols.push({
|
|
641
|
+
name: pkgMatch[1],
|
|
642
|
+
kind: 'namespace',
|
|
643
|
+
line: lineNum,
|
|
644
|
+
endLine: lineNum,
|
|
645
|
+
signature: line.trim(),
|
|
646
|
+
});
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
// ---- Imports ----
|
|
650
|
+
const singleImport = line.match(/^import\s+["']([^"']+)["']/);
|
|
651
|
+
if (singleImport) {
|
|
652
|
+
imports.push({ source: singleImport[1], items: [], line: lineNum, isTypeOnly: false });
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
// Multi-line import block
|
|
656
|
+
if (line.match(/^import\s*\(/)) {
|
|
657
|
+
let j = i + 1;
|
|
658
|
+
while (j < lines.length && !lines[j].includes(')')) {
|
|
659
|
+
const impMatch = lines[j].match(/^\s*(?:(\w+)\s+)?"([^"]+)"/);
|
|
660
|
+
if (impMatch) {
|
|
661
|
+
imports.push({
|
|
662
|
+
source: impMatch[2],
|
|
663
|
+
items: impMatch[1] ? [impMatch[1]] : [],
|
|
664
|
+
line: j + 1,
|
|
665
|
+
isTypeOnly: false,
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
j++;
|
|
669
|
+
}
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
// ---- Functions ----
|
|
673
|
+
const funcMatch = line.match(/^func\s+(?:\(([^)]+)\)\s+)?(\w+)\s*\(([^)]*)\)/);
|
|
674
|
+
if (funcMatch) {
|
|
675
|
+
const [, receiver, name, params] = funcMatch;
|
|
676
|
+
const isExported = /^[A-Z]/.test(name);
|
|
677
|
+
const endLine = findBlockEnd(lines, i, '{');
|
|
678
|
+
const signature = line.trim().replace(/\{$/, '').trim();
|
|
679
|
+
const symbol = {
|
|
680
|
+
name,
|
|
681
|
+
kind: receiver ? 'method' : 'function',
|
|
682
|
+
line: lineNum,
|
|
683
|
+
endLine,
|
|
684
|
+
signature: signature.slice(0, 200),
|
|
685
|
+
modifiers: isExported ? ['exported'] : [],
|
|
686
|
+
};
|
|
687
|
+
symbols.push(symbol);
|
|
688
|
+
if (isExported) {
|
|
689
|
+
exports.push({
|
|
690
|
+
name: receiver ? `(${receiver}).${name}` : name,
|
|
691
|
+
kind: 'function',
|
|
692
|
+
line: lineNum,
|
|
693
|
+
isDefault: false,
|
|
694
|
+
isReexport: false,
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
// ---- Types (structs, interfaces) ----
|
|
700
|
+
const typeMatch = line.match(/^type\s+(\w+)\s+(struct|interface)/);
|
|
701
|
+
if (typeMatch) {
|
|
702
|
+
const name = typeMatch[1];
|
|
703
|
+
const kind = typeMatch[2];
|
|
704
|
+
const isExported = /^[A-Z]/.test(name);
|
|
705
|
+
const endLine = findBlockEnd(lines, i, '{');
|
|
706
|
+
const symbol = {
|
|
707
|
+
name,
|
|
708
|
+
kind: kind === 'struct' ? 'class' : 'interface',
|
|
709
|
+
line: lineNum,
|
|
710
|
+
endLine,
|
|
711
|
+
signature: line.trim().replace(/\{$/, '').trim(),
|
|
712
|
+
modifiers: isExported ? ['exported'] : [],
|
|
713
|
+
children: [],
|
|
714
|
+
};
|
|
715
|
+
// Parse struct/interface members
|
|
716
|
+
const body = lines.slice(i + 1, endLine);
|
|
717
|
+
for (let k = 0; k < body.length; k++) {
|
|
718
|
+
const memberLine = body[k].trim();
|
|
719
|
+
// Struct field
|
|
720
|
+
const fieldMatch = memberLine.match(/^(\w+)\s+[\w*.\[\]]+/);
|
|
721
|
+
if (fieldMatch && !memberLine.startsWith('//') && kind === 'struct') {
|
|
722
|
+
symbol.children.push({
|
|
723
|
+
name: fieldMatch[1],
|
|
724
|
+
kind: 'variable',
|
|
725
|
+
line: lineNum + k + 1,
|
|
726
|
+
endLine: lineNum + k + 1,
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
// Interface method
|
|
730
|
+
const methodMatch = memberLine.match(/^(\w+)\s*\(/);
|
|
731
|
+
if (methodMatch && !memberLine.startsWith('//') && kind === 'interface') {
|
|
732
|
+
symbol.children.push({
|
|
733
|
+
name: methodMatch[1],
|
|
734
|
+
kind: 'method',
|
|
735
|
+
line: lineNum + k + 1,
|
|
736
|
+
endLine: lineNum + k + 1,
|
|
737
|
+
signature: memberLine.replace(/,$/, '').trim(),
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
symbols.push(symbol);
|
|
742
|
+
if (isExported) {
|
|
743
|
+
exports.push({
|
|
744
|
+
name,
|
|
745
|
+
kind: kind === 'struct' ? 'class' : 'interface',
|
|
746
|
+
line: lineNum,
|
|
747
|
+
isDefault: false,
|
|
748
|
+
isReexport: false,
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
// ---- Type aliases ----
|
|
754
|
+
const typeAliasMatch = line.match(/^type\s+(\w+)\s+[^{]/);
|
|
755
|
+
if (typeAliasMatch && !typeMatch) {
|
|
756
|
+
const name = typeAliasMatch[1];
|
|
757
|
+
const isExported = /^[A-Z]/.test(name);
|
|
758
|
+
symbols.push({
|
|
759
|
+
name,
|
|
760
|
+
kind: 'type',
|
|
761
|
+
line: lineNum,
|
|
762
|
+
endLine: lineNum,
|
|
763
|
+
signature: line.trim(),
|
|
764
|
+
modifiers: isExported ? ['exported'] : [],
|
|
765
|
+
});
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
// ---- Variables ----
|
|
769
|
+
const varMatch = line.match(/^var\s+(\w+)\s+/);
|
|
770
|
+
if (varMatch) {
|
|
771
|
+
const name = varMatch[1];
|
|
772
|
+
const isExported = /^[A-Z]/.test(name);
|
|
773
|
+
symbols.push({
|
|
774
|
+
name,
|
|
775
|
+
kind: 'variable',
|
|
776
|
+
line: lineNum,
|
|
777
|
+
endLine: lineNum,
|
|
778
|
+
modifiers: isExported ? ['exported'] : [],
|
|
779
|
+
});
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
// ---- Constants ----
|
|
783
|
+
const constMatch = line.match(/^const\s+(?:\(\s*)?(\w+)/);
|
|
784
|
+
if (constMatch) {
|
|
785
|
+
const name = constMatch[1];
|
|
786
|
+
const isExported = /^[A-Z]/.test(name);
|
|
787
|
+
symbols.push({
|
|
788
|
+
name,
|
|
789
|
+
kind: 'constant',
|
|
790
|
+
line: lineNum,
|
|
791
|
+
endLine: lineNum,
|
|
792
|
+
modifiers: isExported ? ['exported'] : [],
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return { symbols, imports, exports };
|
|
797
|
+
}
|
|
798
|
+
// ---- Rust Parser ----
|
|
799
|
+
function parseRust(content) {
|
|
800
|
+
const lines = content.split('\n');
|
|
801
|
+
const symbols = [];
|
|
802
|
+
const imports = [];
|
|
803
|
+
const exports = [];
|
|
804
|
+
for (let i = 0; i < lines.length; i++) {
|
|
805
|
+
const line = lines[i];
|
|
806
|
+
const lineNum = i + 1;
|
|
807
|
+
// ---- Use statements ----
|
|
808
|
+
const useMatch = line.match(/^use\s+([^;]+);/);
|
|
809
|
+
if (useMatch) {
|
|
810
|
+
const usePath = useMatch[1].replace(/\{[^}]*\}/, '').trim();
|
|
811
|
+
const items = [];
|
|
812
|
+
const namedItems = useMatch[1].match(/\{([^}]+)\}/);
|
|
813
|
+
if (namedItems) {
|
|
814
|
+
items.push(...namedItems[1].split(',').map(s => s.trim().split(/\s+as\s+/)[0].trim()).filter(Boolean));
|
|
815
|
+
}
|
|
816
|
+
imports.push({
|
|
817
|
+
source: usePath.replace(/::\{.*$/, ''),
|
|
818
|
+
items,
|
|
819
|
+
line: lineNum,
|
|
820
|
+
isTypeOnly: false,
|
|
821
|
+
});
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
// ---- Functions ----
|
|
825
|
+
const funcMatch = line.match(/^(pub\s+)?(?:async\s+)?(?:const\s+)?(?:unsafe\s+)?fn\s+(\w+)\s*[<(]/);
|
|
826
|
+
if (funcMatch) {
|
|
827
|
+
const [, pubKw, name] = funcMatch;
|
|
828
|
+
const endLine = findBlockEnd(lines, i, '{');
|
|
829
|
+
const signature = line.trim().replace(/\{$/, '').trim();
|
|
830
|
+
symbols.push({
|
|
831
|
+
name,
|
|
832
|
+
kind: 'function',
|
|
833
|
+
line: lineNum,
|
|
834
|
+
endLine,
|
|
835
|
+
signature: signature.slice(0, 200),
|
|
836
|
+
modifiers: pubKw ? ['pub'] : [],
|
|
837
|
+
});
|
|
838
|
+
if (pubKw) {
|
|
839
|
+
exports.push({
|
|
840
|
+
name,
|
|
841
|
+
kind: 'function',
|
|
842
|
+
line: lineNum,
|
|
843
|
+
isDefault: false,
|
|
844
|
+
isReexport: false,
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
// ---- Structs ----
|
|
850
|
+
const structMatch = line.match(/^(pub\s+)?struct\s+(\w+)/);
|
|
851
|
+
if (structMatch) {
|
|
852
|
+
const [, pubKw, name] = structMatch;
|
|
853
|
+
const endLine = findBlockEnd(lines, i, '{');
|
|
854
|
+
const symbol = {
|
|
855
|
+
name,
|
|
856
|
+
kind: 'class',
|
|
857
|
+
line: lineNum,
|
|
858
|
+
endLine,
|
|
859
|
+
signature: line.trim().replace(/\{$/, '').trim(),
|
|
860
|
+
modifiers: pubKw ? ['pub'] : [],
|
|
861
|
+
children: [],
|
|
862
|
+
};
|
|
863
|
+
// Parse struct fields
|
|
864
|
+
const body = lines.slice(i + 1, endLine);
|
|
865
|
+
for (let k = 0; k < body.length; k++) {
|
|
866
|
+
const fieldMatch = body[k].match(/^\s+(pub\s+)?(\w+)\s*:/);
|
|
867
|
+
if (fieldMatch) {
|
|
868
|
+
symbol.children.push({
|
|
869
|
+
name: fieldMatch[2],
|
|
870
|
+
kind: 'variable',
|
|
871
|
+
line: lineNum + k + 1,
|
|
872
|
+
endLine: lineNum + k + 1,
|
|
873
|
+
modifiers: fieldMatch[1] ? ['pub'] : [],
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
symbols.push(symbol);
|
|
878
|
+
if (pubKw) {
|
|
879
|
+
exports.push({
|
|
880
|
+
name,
|
|
881
|
+
kind: 'class',
|
|
882
|
+
line: lineNum,
|
|
883
|
+
isDefault: false,
|
|
884
|
+
isReexport: false,
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
// ---- Enums ----
|
|
890
|
+
const enumMatch = line.match(/^(pub\s+)?enum\s+(\w+)/);
|
|
891
|
+
if (enumMatch) {
|
|
892
|
+
const [, pubKw, name] = enumMatch;
|
|
893
|
+
const endLine = findBlockEnd(lines, i, '{');
|
|
894
|
+
const symbol = {
|
|
895
|
+
name,
|
|
896
|
+
kind: 'enum',
|
|
897
|
+
line: lineNum,
|
|
898
|
+
endLine,
|
|
899
|
+
signature: line.trim().replace(/\{$/, '').trim(),
|
|
900
|
+
modifiers: pubKw ? ['pub'] : [],
|
|
901
|
+
children: [],
|
|
902
|
+
};
|
|
903
|
+
// Parse enum variants
|
|
904
|
+
const body = lines.slice(i + 1, endLine);
|
|
905
|
+
for (let k = 0; k < body.length; k++) {
|
|
906
|
+
const variantMatch = body[k].match(/^\s+(\w+)/);
|
|
907
|
+
if (variantMatch && !body[k].trim().startsWith('//')) {
|
|
908
|
+
symbol.children.push({
|
|
909
|
+
name: variantMatch[1],
|
|
910
|
+
kind: 'constant',
|
|
911
|
+
line: lineNum + k + 1,
|
|
912
|
+
endLine: lineNum + k + 1,
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
symbols.push(symbol);
|
|
917
|
+
if (pubKw) {
|
|
918
|
+
exports.push({
|
|
919
|
+
name,
|
|
920
|
+
kind: 'enum',
|
|
921
|
+
line: lineNum,
|
|
922
|
+
isDefault: false,
|
|
923
|
+
isReexport: false,
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
// ---- Traits ----
|
|
929
|
+
const traitMatch = line.match(/^(pub\s+)?trait\s+(\w+)/);
|
|
930
|
+
if (traitMatch) {
|
|
931
|
+
const [, pubKw, name] = traitMatch;
|
|
932
|
+
const endLine = findBlockEnd(lines, i, '{');
|
|
933
|
+
const symbol = {
|
|
934
|
+
name,
|
|
935
|
+
kind: 'interface',
|
|
936
|
+
line: lineNum,
|
|
937
|
+
endLine,
|
|
938
|
+
signature: line.trim().replace(/\{$/, '').trim(),
|
|
939
|
+
modifiers: pubKw ? ['pub'] : [],
|
|
940
|
+
children: [],
|
|
941
|
+
};
|
|
942
|
+
// Parse trait methods
|
|
943
|
+
const body = lines.slice(i + 1, endLine);
|
|
944
|
+
for (let k = 0; k < body.length; k++) {
|
|
945
|
+
const methodMatch = body[k].match(/^\s+(?:async\s+)?fn\s+(\w+)/);
|
|
946
|
+
if (methodMatch) {
|
|
947
|
+
symbol.children.push({
|
|
948
|
+
name: methodMatch[1],
|
|
949
|
+
kind: 'method',
|
|
950
|
+
line: lineNum + k + 1,
|
|
951
|
+
endLine: findBlockEnd(body, k, '{') + lineNum + 1,
|
|
952
|
+
signature: body[k].trim().replace(/\{$/, '').trim(),
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
symbols.push(symbol);
|
|
957
|
+
if (pubKw) {
|
|
958
|
+
exports.push({
|
|
959
|
+
name,
|
|
960
|
+
kind: 'interface',
|
|
961
|
+
line: lineNum,
|
|
962
|
+
isDefault: false,
|
|
963
|
+
isReexport: false,
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
// ---- Impl blocks ----
|
|
969
|
+
const implMatch = line.match(/^(pub\s+)?impl\s+(?:<[^>]+>\s*)?(?:trait\s+\w+\s+for\s+)?(\w+)/);
|
|
970
|
+
if (implMatch) {
|
|
971
|
+
const name = implMatch[2];
|
|
972
|
+
const endLine = findBlockEnd(lines, i, '{');
|
|
973
|
+
symbols.push({
|
|
974
|
+
name: `impl ${name}`,
|
|
975
|
+
kind: 'namespace',
|
|
976
|
+
line: lineNum,
|
|
977
|
+
endLine,
|
|
978
|
+
signature: line.trim().replace(/\{$/, '').trim(),
|
|
979
|
+
modifiers: implMatch[1] ? ['pub'] : [],
|
|
980
|
+
});
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
// ---- Type aliases ----
|
|
984
|
+
const typeAliasMatch = line.match(/^(pub\s+)?type\s+(\w+)\s*(?:<[^>]+>)?\s*=/);
|
|
985
|
+
if (typeAliasMatch) {
|
|
986
|
+
const [, pubKw, name] = typeAliasMatch;
|
|
987
|
+
symbols.push({
|
|
988
|
+
name,
|
|
989
|
+
kind: 'type',
|
|
990
|
+
line: lineNum,
|
|
991
|
+
endLine: lineNum,
|
|
992
|
+
signature: line.trim(),
|
|
993
|
+
modifiers: pubKw ? ['pub'] : [],
|
|
994
|
+
});
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
// ---- Static/Const ----
|
|
998
|
+
const staticMatch = line.match(/^(pub\s+)?(?:static|const)\s+(?:mut\s+)?(\w+)/);
|
|
999
|
+
if (staticMatch) {
|
|
1000
|
+
const [, pubKw, name] = staticMatch;
|
|
1001
|
+
symbols.push({
|
|
1002
|
+
name,
|
|
1003
|
+
kind: 'constant',
|
|
1004
|
+
line: lineNum,
|
|
1005
|
+
endLine: lineNum,
|
|
1006
|
+
modifiers: pubKw ? ['pub'] : [],
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
return { symbols, imports, exports };
|
|
1011
|
+
}
|
|
1012
|
+
// ---- Java Parser ----
|
|
1013
|
+
function parseJava(content) {
|
|
1014
|
+
const lines = content.split('\n');
|
|
1015
|
+
const symbols = [];
|
|
1016
|
+
const imports = [];
|
|
1017
|
+
const exports = [];
|
|
1018
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1019
|
+
const line = lines[i];
|
|
1020
|
+
const lineNum = i + 1;
|
|
1021
|
+
// ---- Imports ----
|
|
1022
|
+
const importMatch = line.match(/^import\s+(?:static\s+)?([^;]+);/);
|
|
1023
|
+
if (importMatch) {
|
|
1024
|
+
const source = importMatch[1];
|
|
1025
|
+
const parts = source.split('.');
|
|
1026
|
+
const isStatic = line.includes('import static');
|
|
1027
|
+
imports.push({
|
|
1028
|
+
source: isStatic ? parts.slice(0, -1).join('.') : source,
|
|
1029
|
+
items: isStatic ? [parts[parts.length - 1]] : [],
|
|
1030
|
+
line: lineNum,
|
|
1031
|
+
isTypeOnly: false,
|
|
1032
|
+
});
|
|
1033
|
+
continue;
|
|
1034
|
+
}
|
|
1035
|
+
// ---- Package ----
|
|
1036
|
+
const pkgMatch = line.match(/^package\s+([^;]+);/);
|
|
1037
|
+
if (pkgMatch) {
|
|
1038
|
+
symbols.push({
|
|
1039
|
+
name: pkgMatch[1],
|
|
1040
|
+
kind: 'namespace',
|
|
1041
|
+
line: lineNum,
|
|
1042
|
+
endLine: lineNum,
|
|
1043
|
+
signature: line.trim(),
|
|
1044
|
+
});
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
// ---- Classes ----
|
|
1048
|
+
const classMatch = line.match(/(?:(?:public|private|protected)\s+)?(?:static\s+)?(?:final\s+)?(?:abstract\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([^{]+))?/);
|
|
1049
|
+
if (classMatch) {
|
|
1050
|
+
const name = classMatch[1];
|
|
1051
|
+
const isPublic = line.includes('public ');
|
|
1052
|
+
const endLine = findBlockEnd(lines, i, '{');
|
|
1053
|
+
const modifiers = extractJavaModifiers(line);
|
|
1054
|
+
const signature = line.trim().replace(/\{$/, '').trim();
|
|
1055
|
+
const symbol = {
|
|
1056
|
+
name,
|
|
1057
|
+
kind: 'class',
|
|
1058
|
+
line: lineNum,
|
|
1059
|
+
endLine,
|
|
1060
|
+
signature: signature.slice(0, 200),
|
|
1061
|
+
modifiers,
|
|
1062
|
+
children: [],
|
|
1063
|
+
};
|
|
1064
|
+
// Parse class members
|
|
1065
|
+
const body = lines.slice(i + 1, endLine);
|
|
1066
|
+
for (let k = 0; k < body.length; k++) {
|
|
1067
|
+
const memberLine = body[k];
|
|
1068
|
+
// Methods
|
|
1069
|
+
const methodMatch = memberLine.match(/^\s+(?:(?:public|private|protected|static|final|abstract|synchronized|native|strictfp)\s+)+(?:<[^>]+>\s+)?(?:\w+(?:\[\])*\s+)?(\w+)\s*\(/);
|
|
1070
|
+
if (methodMatch && !memberLine.includes('new ') && !memberLine.match(/^\s+(?:if|while|for|switch|catch)\s*\(/)) {
|
|
1071
|
+
const methodModifiers = extractJavaModifiers(memberLine);
|
|
1072
|
+
symbol.children.push({
|
|
1073
|
+
name: methodMatch[1],
|
|
1074
|
+
kind: 'method',
|
|
1075
|
+
line: lineNum + k + 1,
|
|
1076
|
+
endLine: findBlockEnd(body, k, '{') + lineNum + 1,
|
|
1077
|
+
signature: memberLine.trim().replace(/\{$/, '').trim().slice(0, 200),
|
|
1078
|
+
modifiers: methodModifiers,
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
// Fields (simplified)
|
|
1082
|
+
const fieldMatch = memberLine.match(/^\s+(?:(?:public|private|protected|static|final|volatile|transient)\s+)+(?:\w+(?:<[^>]+>)?(?:\[\])*\s+)(\w+)\s*[;=]/);
|
|
1083
|
+
if (fieldMatch) {
|
|
1084
|
+
symbol.children.push({
|
|
1085
|
+
name: fieldMatch[1],
|
|
1086
|
+
kind: 'variable',
|
|
1087
|
+
line: lineNum + k + 1,
|
|
1088
|
+
endLine: lineNum + k + 1,
|
|
1089
|
+
modifiers: extractJavaModifiers(memberLine),
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
symbols.push(symbol);
|
|
1094
|
+
if (isPublic) {
|
|
1095
|
+
exports.push({
|
|
1096
|
+
name,
|
|
1097
|
+
kind: 'class',
|
|
1098
|
+
line: lineNum,
|
|
1099
|
+
isDefault: false,
|
|
1100
|
+
isReexport: false,
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1105
|
+
// ---- Interfaces ----
|
|
1106
|
+
const ifaceMatch = line.match(/(?:(?:public|private|protected)\s+)?(?:static\s+)?interface\s+(\w+)/);
|
|
1107
|
+
if (ifaceMatch) {
|
|
1108
|
+
const name = ifaceMatch[1];
|
|
1109
|
+
const isPublic = line.includes('public ');
|
|
1110
|
+
const endLine = findBlockEnd(lines, i, '{');
|
|
1111
|
+
const modifiers = extractJavaModifiers(line);
|
|
1112
|
+
const symbol = {
|
|
1113
|
+
name,
|
|
1114
|
+
kind: 'interface',
|
|
1115
|
+
line: lineNum,
|
|
1116
|
+
endLine,
|
|
1117
|
+
signature: line.trim().replace(/\{$/, '').trim(),
|
|
1118
|
+
modifiers,
|
|
1119
|
+
children: [],
|
|
1120
|
+
};
|
|
1121
|
+
// Parse interface methods
|
|
1122
|
+
const body = lines.slice(i + 1, endLine);
|
|
1123
|
+
for (let k = 0; k < body.length; k++) {
|
|
1124
|
+
const methodMatch = body[k].match(/^\s+(?:default\s+|static\s+)?(?:<[^>]+>\s+)?(?:\w+\s+)?(\w+)\s*\(/);
|
|
1125
|
+
if (methodMatch) {
|
|
1126
|
+
symbol.children.push({
|
|
1127
|
+
name: methodMatch[1],
|
|
1128
|
+
kind: 'method',
|
|
1129
|
+
line: lineNum + k + 1,
|
|
1130
|
+
endLine: findBlockEnd(body, k, '{') + lineNum + 1,
|
|
1131
|
+
signature: body[k].trim().replace(/[;{]$/, '').trim(),
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
symbols.push(symbol);
|
|
1136
|
+
if (isPublic) {
|
|
1137
|
+
exports.push({
|
|
1138
|
+
name,
|
|
1139
|
+
kind: 'interface',
|
|
1140
|
+
line: lineNum,
|
|
1141
|
+
isDefault: false,
|
|
1142
|
+
isReexport: false,
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
continue;
|
|
1146
|
+
}
|
|
1147
|
+
// ---- Enums ----
|
|
1148
|
+
const enumMatch = line.match(/(?:(?:public|private|protected)\s+)?(?:static\s+)?enum\s+(\w+)/);
|
|
1149
|
+
if (enumMatch) {
|
|
1150
|
+
const name = enumMatch[1];
|
|
1151
|
+
const endLine = findBlockEnd(lines, i, '{');
|
|
1152
|
+
const modifiers = extractJavaModifiers(line);
|
|
1153
|
+
const symbol = {
|
|
1154
|
+
name,
|
|
1155
|
+
kind: 'enum',
|
|
1156
|
+
line: lineNum,
|
|
1157
|
+
endLine,
|
|
1158
|
+
signature: line.trim().replace(/\{$/, '').trim(),
|
|
1159
|
+
modifiers,
|
|
1160
|
+
children: [],
|
|
1161
|
+
};
|
|
1162
|
+
// Parse enum constants
|
|
1163
|
+
const body = lines.slice(i + 1, endLine);
|
|
1164
|
+
for (let k = 0; k < body.length; k++) {
|
|
1165
|
+
const constantMatch = body[k].match(/^\s+([A-Z_][A-Z_0-9]*)(?:\s*[,(;])/);
|
|
1166
|
+
if (constantMatch) {
|
|
1167
|
+
symbol.children.push({
|
|
1168
|
+
name: constantMatch[1],
|
|
1169
|
+
kind: 'constant',
|
|
1170
|
+
line: lineNum + k + 1,
|
|
1171
|
+
endLine: lineNum + k + 1,
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
symbols.push(symbol);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
return { symbols, imports, exports };
|
|
1179
|
+
}
|
|
1180
|
+
// ---- CSS Parser ----
|
|
1181
|
+
function parseCSS(content) {
|
|
1182
|
+
const lines = content.split('\n');
|
|
1183
|
+
const symbols = [];
|
|
1184
|
+
const imports = [];
|
|
1185
|
+
const exports = [];
|
|
1186
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1187
|
+
const line = lines[i];
|
|
1188
|
+
const lineNum = i + 1;
|
|
1189
|
+
// ---- Imports ----
|
|
1190
|
+
const importMatch = line.match(/@import\s+(?:url\()?['"]?([^'")\s;]+)['"]?\)?/);
|
|
1191
|
+
if (importMatch) {
|
|
1192
|
+
imports.push({
|
|
1193
|
+
source: importMatch[1],
|
|
1194
|
+
items: [],
|
|
1195
|
+
line: lineNum,
|
|
1196
|
+
isTypeOnly: false,
|
|
1197
|
+
});
|
|
1198
|
+
continue;
|
|
1199
|
+
}
|
|
1200
|
+
// ---- Selectors ----
|
|
1201
|
+
const selectorMatch = line.match(/^([.#][\w-]+|[\w-]+\s*\{)/);
|
|
1202
|
+
if (selectorMatch) {
|
|
1203
|
+
const name = selectorMatch[1].replace(/\s*\{$/, '').trim();
|
|
1204
|
+
const kind = name.startsWith('.') ? 'variable' : name.startsWith('#') ? 'constant' : 'class';
|
|
1205
|
+
const endLine = findBlockEnd(lines, i, '{');
|
|
1206
|
+
symbols.push({
|
|
1207
|
+
name,
|
|
1208
|
+
kind,
|
|
1209
|
+
line: lineNum,
|
|
1210
|
+
endLine,
|
|
1211
|
+
signature: line.trim(),
|
|
1212
|
+
});
|
|
1213
|
+
continue;
|
|
1214
|
+
}
|
|
1215
|
+
// ---- Media queries ----
|
|
1216
|
+
const mediaMatch = line.match(/^@media\s+([^{]+)\s*\{/);
|
|
1217
|
+
if (mediaMatch) {
|
|
1218
|
+
const endLine = findBlockEnd(lines, i, '{');
|
|
1219
|
+
symbols.push({
|
|
1220
|
+
name: `@media ${mediaMatch[1].trim()}`,
|
|
1221
|
+
kind: 'namespace',
|
|
1222
|
+
line: lineNum,
|
|
1223
|
+
endLine,
|
|
1224
|
+
signature: line.trim(),
|
|
1225
|
+
});
|
|
1226
|
+
continue;
|
|
1227
|
+
}
|
|
1228
|
+
// ---- Keyframes ----
|
|
1229
|
+
const keyframesMatch = line.match(/^@keyframes\s+([\w-]+)/);
|
|
1230
|
+
if (keyframesMatch) {
|
|
1231
|
+
const endLine = findBlockEnd(lines, i, '{');
|
|
1232
|
+
symbols.push({
|
|
1233
|
+
name: keyframesMatch[1],
|
|
1234
|
+
kind: 'function',
|
|
1235
|
+
line: lineNum,
|
|
1236
|
+
endLine,
|
|
1237
|
+
signature: line.trim(),
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
return { symbols, imports, exports };
|
|
1242
|
+
}
|
|
1243
|
+
// ---- HTML Parser ----
|
|
1244
|
+
function parseHTML(content) {
|
|
1245
|
+
const lines = content.split('\n');
|
|
1246
|
+
const symbols = [];
|
|
1247
|
+
const imports = [];
|
|
1248
|
+
const exports = [];
|
|
1249
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1250
|
+
const line = lines[i];
|
|
1251
|
+
const lineNum = i + 1;
|
|
1252
|
+
// ---- Script imports ----
|
|
1253
|
+
const scriptSrc = line.match(/<script[^>]+src=["']([^"']+)["']/);
|
|
1254
|
+
if (scriptSrc) {
|
|
1255
|
+
imports.push({
|
|
1256
|
+
source: scriptSrc[1],
|
|
1257
|
+
items: [],
|
|
1258
|
+
line: lineNum,
|
|
1259
|
+
isTypeOnly: false,
|
|
1260
|
+
});
|
|
1261
|
+
continue;
|
|
1262
|
+
}
|
|
1263
|
+
// ---- Style imports ----
|
|
1264
|
+
const styleHref = line.match(/<link[^>]+href=["']([^"']+)["'][^>]*rel=["']stylesheet["']/);
|
|
1265
|
+
if (styleHref) {
|
|
1266
|
+
imports.push({
|
|
1267
|
+
source: styleHref[1],
|
|
1268
|
+
items: [],
|
|
1269
|
+
line: lineNum,
|
|
1270
|
+
isTypeOnly: false,
|
|
1271
|
+
});
|
|
1272
|
+
continue;
|
|
1273
|
+
}
|
|
1274
|
+
// ---- IDs ----
|
|
1275
|
+
const idMatch = line.match(/id=["']([^"']+)["']/);
|
|
1276
|
+
if (idMatch) {
|
|
1277
|
+
symbols.push({
|
|
1278
|
+
name: `#${idMatch[1]}`,
|
|
1279
|
+
kind: 'constant',
|
|
1280
|
+
line: lineNum,
|
|
1281
|
+
endLine: lineNum,
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
// ---- Template/component tags ----
|
|
1285
|
+
const templateMatch = line.match(/<template\s+id=["']([^"']+)["']/);
|
|
1286
|
+
if (templateMatch) {
|
|
1287
|
+
const endLine = findHTMLTagEnd(lines, i, 'template');
|
|
1288
|
+
symbols.push({
|
|
1289
|
+
name: templateMatch[1],
|
|
1290
|
+
kind: 'class',
|
|
1291
|
+
line: lineNum,
|
|
1292
|
+
endLine,
|
|
1293
|
+
signature: line.trim(),
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
return { symbols, imports, exports };
|
|
1298
|
+
}
|
|
1299
|
+
// ============================================================
|
|
1300
|
+
// Helper Functions for Block Detection
|
|
1301
|
+
// ============================================================
|
|
1302
|
+
function findBlockEnd(lines, startLine, openChar = '{') {
|
|
1303
|
+
let depth = 0;
|
|
1304
|
+
const closeChar = openChar === '{' ? '}' : openChar === '(' ? ')' : openChar === '[' ? ']' : '';
|
|
1305
|
+
for (let i = startLine; i < lines.length; i++) {
|
|
1306
|
+
const line = lines[i];
|
|
1307
|
+
// Skip strings (rough approximation)
|
|
1308
|
+
const cleaned = line.replace(/(["'`])(?:(?!\1).|\\\1)*\1/g, '');
|
|
1309
|
+
for (const ch of cleaned) {
|
|
1310
|
+
if (ch === openChar)
|
|
1311
|
+
depth++;
|
|
1312
|
+
if (ch === closeChar)
|
|
1313
|
+
depth--;
|
|
1314
|
+
}
|
|
1315
|
+
if (depth <= 0 && i > startLine) {
|
|
1316
|
+
return i + 1;
|
|
1317
|
+
}
|
|
1318
|
+
// Handle single-line blocks
|
|
1319
|
+
if (i === startLine && depth === 0) {
|
|
1320
|
+
return i + 1;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
return lines.length;
|
|
1324
|
+
}
|
|
1325
|
+
function findTypeEnd(lines, startLine) {
|
|
1326
|
+
// Type aliases can span multiple lines with unions/intersections
|
|
1327
|
+
let depth = 0;
|
|
1328
|
+
let foundEquals = false;
|
|
1329
|
+
for (let i = startLine; i < lines.length; i++) {
|
|
1330
|
+
const line = lines[i];
|
|
1331
|
+
if (line.includes('='))
|
|
1332
|
+
foundEquals = true;
|
|
1333
|
+
if (foundEquals) {
|
|
1334
|
+
// Count parentheses and brackets for generics
|
|
1335
|
+
for (const ch of line) {
|
|
1336
|
+
if (ch === '(' || ch === '[' || ch === '{')
|
|
1337
|
+
depth++;
|
|
1338
|
+
if (ch === ')' || ch === ']' || ch === '}')
|
|
1339
|
+
depth--;
|
|
1340
|
+
}
|
|
1341
|
+
// End of type when depth is 0 and line doesn't end with continuation
|
|
1342
|
+
if (depth <= 0 && !line.trim().endsWith('|') && !line.trim().endsWith('&') && i > startLine) {
|
|
1343
|
+
return i + 1;
|
|
1344
|
+
}
|
|
1345
|
+
// Simple single-line type
|
|
1346
|
+
if (depth === 0 && i === startLine && !line.trim().endsWith('|') && !line.trim().endsWith('&')) {
|
|
1347
|
+
return i + 1;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
return lines.length;
|
|
1352
|
+
}
|
|
1353
|
+
function findStatementEnd(lines, startLine) {
|
|
1354
|
+
// For const/let arrow functions, find the end of the statement
|
|
1355
|
+
let depth = 0;
|
|
1356
|
+
let parenDepth = 0;
|
|
1357
|
+
for (let i = startLine; i < lines.length; i++) {
|
|
1358
|
+
const line = lines[i];
|
|
1359
|
+
const cleaned = line.replace(/(["'`])(?:(?!\1).|\\\1)*\1/g, '');
|
|
1360
|
+
for (const ch of cleaned) {
|
|
1361
|
+
if (ch === '{')
|
|
1362
|
+
depth++;
|
|
1363
|
+
if (ch === '}')
|
|
1364
|
+
depth--;
|
|
1365
|
+
if (ch === '(')
|
|
1366
|
+
parenDepth++;
|
|
1367
|
+
if (ch === ')')
|
|
1368
|
+
parenDepth--;
|
|
1369
|
+
}
|
|
1370
|
+
if (depth === 0 && parenDepth === 0 && i > startLine) {
|
|
1371
|
+
return i + 1;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
return Math.min(startLine + 30, lines.length);
|
|
1375
|
+
}
|
|
1376
|
+
function findPythonBlockEnd(lines, startLine) {
|
|
1377
|
+
if (startLine >= lines.length)
|
|
1378
|
+
return lines.length;
|
|
1379
|
+
// Find the indentation of the block start
|
|
1380
|
+
const startIndent = lines[startLine].match(/^(\s*)/)?.[1].length || 0;
|
|
1381
|
+
for (let i = startLine + 1; i < lines.length; i++) {
|
|
1382
|
+
const line = lines[i];
|
|
1383
|
+
if (line.trim() === '')
|
|
1384
|
+
continue;
|
|
1385
|
+
const currentIndent = line.match(/^(\s*)/)?.[1].length || 0;
|
|
1386
|
+
if (currentIndent <= startIndent && line.trim() !== '') {
|
|
1387
|
+
return i;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
return lines.length;
|
|
1391
|
+
}
|
|
1392
|
+
function findHTMLTagEnd(lines, startLine, tagName) {
|
|
1393
|
+
const closeTag = `</${tagName}>`;
|
|
1394
|
+
for (let i = startLine + 1; i < lines.length; i++) {
|
|
1395
|
+
if (lines[i].includes(closeTag)) {
|
|
1396
|
+
return i + 1;
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
return lines.length;
|
|
1400
|
+
}
|
|
1401
|
+
function extractModifiers(line) {
|
|
1402
|
+
const modifiers = [];
|
|
1403
|
+
const modifierKeywords = ['export', 'default', 'declare', 'abstract', 'async', 'static', 'public', 'private', 'protected', 'readonly', 'override', 'const', 'let', 'var'];
|
|
1404
|
+
for (const kw of modifierKeywords) {
|
|
1405
|
+
// Use word boundary check
|
|
1406
|
+
const regex = new RegExp(`\\b${kw}\\b`);
|
|
1407
|
+
if (regex.test(line)) {
|
|
1408
|
+
modifiers.push(kw);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
return modifiers;
|
|
1412
|
+
}
|
|
1413
|
+
function extractJavaModifiers(line) {
|
|
1414
|
+
const modifiers = [];
|
|
1415
|
+
const modifierKeywords = ['public', 'private', 'protected', 'static', 'final', 'abstract', 'synchronized', 'native', 'strictfp', 'volatile', 'transient', 'default'];
|
|
1416
|
+
for (const kw of modifierKeywords) {
|
|
1417
|
+
const regex = new RegExp(`\\b${kw}\\b`);
|
|
1418
|
+
if (regex.test(line)) {
|
|
1419
|
+
modifiers.push(kw);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
return modifiers;
|
|
1423
|
+
}
|
|
1424
|
+
// ============================================================
|
|
1425
|
+
// Main Integration Class
|
|
1426
|
+
// ============================================================
|
|
1427
|
+
export class TreeSitterIntegration {
|
|
1428
|
+
projectRoot;
|
|
1429
|
+
fileCache = new Map();
|
|
1430
|
+
repoMapCache = null;
|
|
1431
|
+
repoMapCacheTime = 0;
|
|
1432
|
+
CACHE_TTL = 60000; // 1 minute
|
|
1433
|
+
constructor(projectRoot) {
|
|
1434
|
+
this.projectRoot = projectRoot;
|
|
1435
|
+
}
|
|
1436
|
+
// ---- Core Analysis Methods ----
|
|
1437
|
+
analyzeFile(filePath) {
|
|
1438
|
+
const absolutePath = this.resolvePath(filePath);
|
|
1439
|
+
if (!existsSync(absolutePath))
|
|
1440
|
+
return null;
|
|
1441
|
+
const content = this.readFile(absolutePath);
|
|
1442
|
+
if (content === null)
|
|
1443
|
+
return null;
|
|
1444
|
+
const language = this.detectLanguage(absolutePath);
|
|
1445
|
+
if (language === 'unknown')
|
|
1446
|
+
return null;
|
|
1447
|
+
const parsed = this.parseContent(content, language);
|
|
1448
|
+
const summary = this.generateFileSummary(absolutePath, language, parsed);
|
|
1449
|
+
return {
|
|
1450
|
+
path: relative(this.projectRoot, absolutePath),
|
|
1451
|
+
language,
|
|
1452
|
+
symbols: parsed.symbols,
|
|
1453
|
+
imports: parsed.imports,
|
|
1454
|
+
exports: parsed.exports,
|
|
1455
|
+
summary,
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
analyzeDirectory(dirPath, maxDepth = 5) {
|
|
1459
|
+
const absoluteDir = this.resolvePath(dirPath);
|
|
1460
|
+
if (!existsSync(absoluteDir) || !statSync(absoluteDir).isDirectory())
|
|
1461
|
+
return [];
|
|
1462
|
+
const files = [];
|
|
1463
|
+
this.walkDirectory(absoluteDir, files, 0, maxDepth);
|
|
1464
|
+
return files;
|
|
1465
|
+
}
|
|
1466
|
+
buildRepoMap(projectRoot) {
|
|
1467
|
+
const root = projectRoot ? this.resolvePath(projectRoot) : this.projectRoot;
|
|
1468
|
+
// Check cache
|
|
1469
|
+
if (this.repoMapCache && Date.now() - this.repoMapCacheTime < this.CACHE_TTL) {
|
|
1470
|
+
return this.repoMapCache;
|
|
1471
|
+
}
|
|
1472
|
+
const files = this.analyzeDirectory(root);
|
|
1473
|
+
const languageBreakdown = {};
|
|
1474
|
+
let totalSymbols = 0;
|
|
1475
|
+
for (const file of files) {
|
|
1476
|
+
const langName = LANGUAGE_NAMES[file.language] || file.language;
|
|
1477
|
+
languageBreakdown[langName] = (languageBreakdown[langName] || 0) + 1;
|
|
1478
|
+
totalSymbols += file.symbols.length;
|
|
1479
|
+
}
|
|
1480
|
+
this.repoMapCache = {
|
|
1481
|
+
root,
|
|
1482
|
+
files,
|
|
1483
|
+
totalSymbols,
|
|
1484
|
+
totalFiles: files.length,
|
|
1485
|
+
languageBreakdown,
|
|
1486
|
+
generatedAt: Date.now(),
|
|
1487
|
+
};
|
|
1488
|
+
this.repoMapCacheTime = Date.now();
|
|
1489
|
+
return this.repoMapCache;
|
|
1490
|
+
}
|
|
1491
|
+
// ---- Symbol Methods ----
|
|
1492
|
+
getSymbols(filePath) {
|
|
1493
|
+
const fileMap = this.analyzeFile(filePath);
|
|
1494
|
+
return fileMap?.symbols || [];
|
|
1495
|
+
}
|
|
1496
|
+
getCallGraph(filePath) {
|
|
1497
|
+
const absolutePath = this.resolvePath(filePath);
|
|
1498
|
+
const content = this.readFile(absolutePath);
|
|
1499
|
+
if (!content)
|
|
1500
|
+
return [];
|
|
1501
|
+
const language = this.detectLanguage(absolutePath);
|
|
1502
|
+
const parsed = this.parseContent(content, language);
|
|
1503
|
+
const nodes = [];
|
|
1504
|
+
for (const symbol of parsed.symbols) {
|
|
1505
|
+
if (symbol.kind === 'function' || symbol.kind === 'method') {
|
|
1506
|
+
// Extract function calls from the function body
|
|
1507
|
+
const lines = content.split('\n');
|
|
1508
|
+
const body = lines.slice(symbol.line - 1, symbol.endLine).join('\n');
|
|
1509
|
+
const calls = this.extractCalls(body, language);
|
|
1510
|
+
nodes.push({
|
|
1511
|
+
name: symbol.name,
|
|
1512
|
+
kind: symbol.kind,
|
|
1513
|
+
line: symbol.line,
|
|
1514
|
+
calls: Array.from(new Set(calls)),
|
|
1515
|
+
calledBy: [], // Filled in by cross-referencing
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
// Cross-reference: who calls whom
|
|
1520
|
+
const allCalls = new Map();
|
|
1521
|
+
for (const node of nodes) {
|
|
1522
|
+
for (const call of node.calls) {
|
|
1523
|
+
if (!allCalls.has(call))
|
|
1524
|
+
allCalls.set(call, new Set());
|
|
1525
|
+
allCalls.get(call).add(node.name);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
for (const node of nodes) {
|
|
1529
|
+
const callers = allCalls.get(node.name);
|
|
1530
|
+
node.calledBy = callers ? Array.from(callers) : [];
|
|
1531
|
+
}
|
|
1532
|
+
return nodes;
|
|
1533
|
+
}
|
|
1534
|
+
getDependencies(filePath) {
|
|
1535
|
+
const fileMap = this.analyzeFile(filePath);
|
|
1536
|
+
return fileMap?.imports || [];
|
|
1537
|
+
}
|
|
1538
|
+
findDefinition(symbol, projectRoot) {
|
|
1539
|
+
const root = projectRoot ? this.resolvePath(projectRoot) : this.projectRoot;
|
|
1540
|
+
const files = this.analyzeDirectory(root);
|
|
1541
|
+
const results = [];
|
|
1542
|
+
for (const file of files) {
|
|
1543
|
+
for (const sym of file.symbols) {
|
|
1544
|
+
if (sym.name === symbol) {
|
|
1545
|
+
results.push({
|
|
1546
|
+
file: file.path,
|
|
1547
|
+
line: sym.line,
|
|
1548
|
+
kind: sym.kind,
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
// Check children
|
|
1552
|
+
if (sym.children) {
|
|
1553
|
+
for (const child of sym.children) {
|
|
1554
|
+
if (child.name === symbol) {
|
|
1555
|
+
results.push({
|
|
1556
|
+
file: file.path,
|
|
1557
|
+
line: child.line,
|
|
1558
|
+
kind: child.kind,
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
return results;
|
|
1566
|
+
}
|
|
1567
|
+
findReferences(symbol, projectRoot) {
|
|
1568
|
+
const root = projectRoot ? this.resolvePath(projectRoot) : this.projectRoot;
|
|
1569
|
+
const results = [];
|
|
1570
|
+
// Try using ripgrep first for speed
|
|
1571
|
+
try {
|
|
1572
|
+
const rgResult = execSync(`rg --line-number --color never --max-count 50 "\\b${symbol}\\b" "${root}"`, { encoding: 'utf-8', timeout: 30000, cwd: root });
|
|
1573
|
+
for (const line of rgResult.trim().split('\n')) {
|
|
1574
|
+
const match = line.match(/^(.+?):(\d+):(.+)$/);
|
|
1575
|
+
if (match) {
|
|
1576
|
+
const [, file, lineNum, context] = match;
|
|
1577
|
+
// Skip binary files and common non-source directories
|
|
1578
|
+
const ext = extname(file);
|
|
1579
|
+
if (!BINARY_EXTENSIONS.has(ext) && !SKIP_DIRS.has(dirname(file).split('/')[0])) {
|
|
1580
|
+
results.push({
|
|
1581
|
+
file: relative(root, file),
|
|
1582
|
+
line: parseInt(lineNum, 10),
|
|
1583
|
+
context: context.trim(),
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
catch {
|
|
1590
|
+
// Fallback: search in analyzed files
|
|
1591
|
+
const files = this.analyzeDirectory(root);
|
|
1592
|
+
for (const file of files) {
|
|
1593
|
+
const absolutePath = join(root, file.path);
|
|
1594
|
+
const content = this.readFile(absolutePath);
|
|
1595
|
+
if (!content)
|
|
1596
|
+
continue;
|
|
1597
|
+
const lines = content.split('\n');
|
|
1598
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1599
|
+
if (new RegExp(`\\b${symbol}\\b`).test(lines[i])) {
|
|
1600
|
+
results.push({
|
|
1601
|
+
file: file.path,
|
|
1602
|
+
line: i + 1,
|
|
1603
|
+
context: lines[i].trim(),
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
return results.slice(0, 100);
|
|
1610
|
+
}
|
|
1611
|
+
getOutline(filePath) {
|
|
1612
|
+
const fileMap = this.analyzeFile(filePath);
|
|
1613
|
+
if (!fileMap)
|
|
1614
|
+
return [];
|
|
1615
|
+
return fileMap.symbols.map(sym => this.symbolToOutline(sym));
|
|
1616
|
+
}
|
|
1617
|
+
detectLanguage(filePath) {
|
|
1618
|
+
const ext = extname(filePath).toLowerCase();
|
|
1619
|
+
return LANGUAGE_EXTENSIONS[ext] || 'unknown';
|
|
1620
|
+
}
|
|
1621
|
+
getDiagnostics(filePath) {
|
|
1622
|
+
const absolutePath = this.resolvePath(filePath);
|
|
1623
|
+
const content = this.readFile(absolutePath);
|
|
1624
|
+
if (!content)
|
|
1625
|
+
return [];
|
|
1626
|
+
const language = this.detectLanguage(absolutePath);
|
|
1627
|
+
const diagnostics = [];
|
|
1628
|
+
const lines = content.split('\n');
|
|
1629
|
+
// Basic syntax checks - common issues across languages
|
|
1630
|
+
// 1. Unmatched brackets
|
|
1631
|
+
const bracketStack = [];
|
|
1632
|
+
const pairs = { '(': ')', '[': ']', '{': '}' };
|
|
1633
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1634
|
+
const line = lines[i];
|
|
1635
|
+
// Skip string content (rough)
|
|
1636
|
+
const cleaned = line.replace(/(["'`])(?:(?!\1).|\\\1)*\1/g, '').replace(/\/\/.*$|\/\*[\s\S]*?\*\//g, '');
|
|
1637
|
+
for (let j = 0; j < cleaned.length; j++) {
|
|
1638
|
+
const ch = cleaned[j];
|
|
1639
|
+
if (pairs[ch]) {
|
|
1640
|
+
bracketStack.push({ char: ch, line: i + 1, col: j + 1 });
|
|
1641
|
+
}
|
|
1642
|
+
else if (Object.values(pairs).includes(ch)) {
|
|
1643
|
+
const last = bracketStack.pop();
|
|
1644
|
+
if (!last) {
|
|
1645
|
+
diagnostics.push({
|
|
1646
|
+
line: i + 1,
|
|
1647
|
+
column: j + 1,
|
|
1648
|
+
severity: 'warning',
|
|
1649
|
+
message: `Unexpected closing bracket '${ch}'`,
|
|
1650
|
+
rule: 'unmatched-bracket',
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
else if (pairs[last.char] !== ch) {
|
|
1654
|
+
diagnostics.push({
|
|
1655
|
+
line: i + 1,
|
|
1656
|
+
column: j + 1,
|
|
1657
|
+
severity: 'warning',
|
|
1658
|
+
message: `Mismatched bracket: expected '${pairs[last.char]}' but found '${ch}' (opened at line ${last.line})`,
|
|
1659
|
+
rule: 'mismatched-bracket',
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
// Remaining unclosed brackets
|
|
1666
|
+
for (const bracket of bracketStack) {
|
|
1667
|
+
diagnostics.push({
|
|
1668
|
+
line: bracket.line,
|
|
1669
|
+
column: bracket.col,
|
|
1670
|
+
severity: 'warning',
|
|
1671
|
+
message: `Unclosed bracket '${bracket.char}'`,
|
|
1672
|
+
rule: 'unclosed-bracket',
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1675
|
+
// 2. Language-specific checks
|
|
1676
|
+
if (language === 'typescript' || language === 'javascript') {
|
|
1677
|
+
// Check for missing semicolons in specific patterns
|
|
1678
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1679
|
+
const trimmed = lines[i].trim();
|
|
1680
|
+
if (trimmed.length > 0 && !trimmed.startsWith('//') && !trimmed.startsWith('*') && !trimmed.startsWith('/*')) {
|
|
1681
|
+
// Detect potential issues
|
|
1682
|
+
if (/^(?:export\s+)?(?:const|let|var)\s+\w+\s*=\s*[^;{]+$/.test(trimmed) && !trimmed.endsWith(',')) {
|
|
1683
|
+
// This could be a missing semicolon, but it's too noisy to report all
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
if (language === 'python') {
|
|
1689
|
+
// Check for mixed tabs/spaces
|
|
1690
|
+
let usesTabs = false;
|
|
1691
|
+
let usesSpaces = false;
|
|
1692
|
+
for (const line of lines) {
|
|
1693
|
+
if (line.startsWith('\t'))
|
|
1694
|
+
usesTabs = true;
|
|
1695
|
+
if (line.startsWith(' '))
|
|
1696
|
+
usesSpaces = true;
|
|
1697
|
+
}
|
|
1698
|
+
if (usesTabs && usesSpaces) {
|
|
1699
|
+
diagnostics.push({
|
|
1700
|
+
line: 1,
|
|
1701
|
+
column: 1,
|
|
1702
|
+
severity: 'warning',
|
|
1703
|
+
message: 'Mixed tabs and spaces for indentation',
|
|
1704
|
+
rule: 'mixed-indentation',
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
return diagnostics;
|
|
1709
|
+
}
|
|
1710
|
+
formatRepoMap(projectRoot, maxTokens) {
|
|
1711
|
+
const repoMap = this.buildRepoMap(projectRoot);
|
|
1712
|
+
const maxChars = (maxTokens || 4000) * 4; // ~4 chars per token
|
|
1713
|
+
const lines = [];
|
|
1714
|
+
lines.push(`# Repository Map: ${basename(repoMap.root)}`);
|
|
1715
|
+
lines.push(`# ${repoMap.totalFiles} files | ${repoMap.totalSymbols} symbols | Generated: ${new Date(repoMap.generatedAt).toISOString()}`);
|
|
1716
|
+
lines.push('');
|
|
1717
|
+
// Language breakdown
|
|
1718
|
+
const langEntries = Object.entries(repoMap.languageBreakdown)
|
|
1719
|
+
.sort(([, a], [, b]) => b - a);
|
|
1720
|
+
lines.push(`Languages: ${langEntries.map(([l, c]) => `${l}(${c})`).join(', ')}`);
|
|
1721
|
+
lines.push('');
|
|
1722
|
+
// File listing with symbols
|
|
1723
|
+
for (const file of repoMap.files) {
|
|
1724
|
+
const langName = LANGUAGE_NAMES[file.language] || file.language;
|
|
1725
|
+
const relPath = file.path;
|
|
1726
|
+
// File header
|
|
1727
|
+
const importCount = file.imports.length;
|
|
1728
|
+
const exportCount = file.exports.length;
|
|
1729
|
+
lines.push(`## ${relPath} [${langName}]`);
|
|
1730
|
+
if (importCount > 0 || exportCount > 0) {
|
|
1731
|
+
lines.push(` imports: ${importCount} | exports: ${exportCount}`);
|
|
1732
|
+
}
|
|
1733
|
+
// Symbols
|
|
1734
|
+
for (const sym of file.symbols) {
|
|
1735
|
+
const modifier = sym.modifiers?.length ? `[${sym.modifiers.join(',')}] ` : '';
|
|
1736
|
+
const icon = this.getSymbolIcon(sym.kind);
|
|
1737
|
+
const lineRange = sym.line === sym.endLine ? `:${sym.line}` : `:${sym.line}-${sym.endLine}`;
|
|
1738
|
+
const sig = sym.signature ? ` — ${sym.signature.slice(0, 80)}` : '';
|
|
1739
|
+
lines.push(` ${icon} ${modifier}${sym.kind} ${sym.name}${lineRange}${sig}`);
|
|
1740
|
+
// Children
|
|
1741
|
+
if (sym.children) {
|
|
1742
|
+
for (const child of sym.children) {
|
|
1743
|
+
const childMod = child.modifiers?.length ? `[${child.modifiers.join(',')}] ` : '';
|
|
1744
|
+
const childIcon = this.getSymbolIcon(child.kind);
|
|
1745
|
+
const childRange = child.line === child.endLine ? `:${child.line}` : `:${child.line}-${child.endLine}`;
|
|
1746
|
+
lines.push(` ${childIcon} ${childMod}${child.kind} ${child.name}${childRange}`);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
lines.push('');
|
|
1751
|
+
// Check if we've exceeded the token limit
|
|
1752
|
+
const currentLength = lines.join('\n').length;
|
|
1753
|
+
if (currentLength > maxChars) {
|
|
1754
|
+
lines.push(`... [truncated, ${repoMap.files.length - repoMap.files.indexOf(file) - 1} more files]`);
|
|
1755
|
+
break;
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
return lines.join('\n');
|
|
1759
|
+
}
|
|
1760
|
+
// ---- Cache Management ----
|
|
1761
|
+
invalidateCache(filePath) {
|
|
1762
|
+
if (filePath) {
|
|
1763
|
+
this.fileCache.delete(this.resolvePath(filePath));
|
|
1764
|
+
}
|
|
1765
|
+
else {
|
|
1766
|
+
this.fileCache.clear();
|
|
1767
|
+
}
|
|
1768
|
+
this.repoMapCache = null;
|
|
1769
|
+
this.repoMapCacheTime = 0;
|
|
1770
|
+
}
|
|
1771
|
+
// ---- Private Methods ----
|
|
1772
|
+
resolvePath(filePath) {
|
|
1773
|
+
if (filePath.startsWith('/'))
|
|
1774
|
+
return filePath;
|
|
1775
|
+
return join(this.projectRoot, filePath);
|
|
1776
|
+
}
|
|
1777
|
+
readFile(absolutePath) {
|
|
1778
|
+
try {
|
|
1779
|
+
if (!existsSync(absolutePath))
|
|
1780
|
+
return null;
|
|
1781
|
+
const stat = statSync(absolutePath);
|
|
1782
|
+
if (stat.isDirectory())
|
|
1783
|
+
return null;
|
|
1784
|
+
if (stat.size > 1024 * 1024)
|
|
1785
|
+
return null; // Skip files > 1MB
|
|
1786
|
+
return readFileSync(absolutePath, 'utf-8');
|
|
1787
|
+
}
|
|
1788
|
+
catch {
|
|
1789
|
+
return null;
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
parseContent(content, language) {
|
|
1793
|
+
switch (language) {
|
|
1794
|
+
case 'typescript':
|
|
1795
|
+
case 'javascript':
|
|
1796
|
+
return parseTypeScript(content);
|
|
1797
|
+
case 'python':
|
|
1798
|
+
return parsePython(content);
|
|
1799
|
+
case 'go':
|
|
1800
|
+
return parseGo(content);
|
|
1801
|
+
case 'rust':
|
|
1802
|
+
return parseRust(content);
|
|
1803
|
+
case 'java':
|
|
1804
|
+
case 'kotlin':
|
|
1805
|
+
return parseJava(content);
|
|
1806
|
+
case 'css':
|
|
1807
|
+
return parseCSS(content);
|
|
1808
|
+
case 'html':
|
|
1809
|
+
return parseHTML(content);
|
|
1810
|
+
default:
|
|
1811
|
+
return { symbols: [], imports: [], exports: [] };
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
walkDirectory(dir, results, depth, maxDepth) {
|
|
1815
|
+
if (depth > maxDepth)
|
|
1816
|
+
return;
|
|
1817
|
+
let entries;
|
|
1818
|
+
try {
|
|
1819
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
1820
|
+
}
|
|
1821
|
+
catch {
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
for (const entry of entries) {
|
|
1825
|
+
if (SKIP_DIRS.has(entry.name))
|
|
1826
|
+
continue;
|
|
1827
|
+
if (entry.name.startsWith('.') && entry.name !== '.env')
|
|
1828
|
+
continue;
|
|
1829
|
+
const fullPath = join(dir, entry.name);
|
|
1830
|
+
if (entry.isDirectory()) {
|
|
1831
|
+
this.walkDirectory(fullPath, results, depth + 1, maxDepth);
|
|
1832
|
+
}
|
|
1833
|
+
else if (entry.isFile()) {
|
|
1834
|
+
const ext = extname(entry.name).toLowerCase();
|
|
1835
|
+
if (BINARY_EXTENSIONS.has(ext))
|
|
1836
|
+
continue;
|
|
1837
|
+
const language = LANGUAGE_EXTENSIONS[ext];
|
|
1838
|
+
if (!language)
|
|
1839
|
+
continue;
|
|
1840
|
+
const fileMap = this.analyzeFile(fullPath);
|
|
1841
|
+
if (fileMap) {
|
|
1842
|
+
results.push(fileMap);
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
generateFileSummary(filePath, language, parsed) {
|
|
1848
|
+
const parts = [];
|
|
1849
|
+
const langName = LANGUAGE_NAMES[language] || language;
|
|
1850
|
+
parts.push(langName);
|
|
1851
|
+
if (parsed.symbols.length > 0) {
|
|
1852
|
+
const kinds = parsed.symbols.map(s => s.kind);
|
|
1853
|
+
const classCount = kinds.filter(k => k === 'class').length;
|
|
1854
|
+
const funcCount = kinds.filter(k => k === 'function').length;
|
|
1855
|
+
const ifaceCount = kinds.filter(k => k === 'interface').length;
|
|
1856
|
+
const typeCount = kinds.filter(k => k === 'type').length;
|
|
1857
|
+
const enumCount = kinds.filter(k => k === 'enum').length;
|
|
1858
|
+
const details = [];
|
|
1859
|
+
if (classCount)
|
|
1860
|
+
details.push(`${classCount} class${classCount > 1 ? 'es' : ''}`);
|
|
1861
|
+
if (funcCount)
|
|
1862
|
+
details.push(`${funcCount} fn${funcCount > 1 ? 's' : ''}`);
|
|
1863
|
+
if (ifaceCount)
|
|
1864
|
+
details.push(`${ifaceCount} iface${ifaceCount > 1 ? 's' : ''}`);
|
|
1865
|
+
if (typeCount)
|
|
1866
|
+
details.push(`${typeCount} type${typeCount > 1 ? 's' : ''}`);
|
|
1867
|
+
if (enumCount)
|
|
1868
|
+
details.push(`${enumCount} enum${enumCount > 1 ? 's' : ''}`);
|
|
1869
|
+
if (details.length > 0) {
|
|
1870
|
+
parts.push(details.join(', '));
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
if (parsed.imports.length > 0) {
|
|
1874
|
+
parts.push(`${parsed.imports.length} import${parsed.imports.length > 1 ? 's' : ''}`);
|
|
1875
|
+
}
|
|
1876
|
+
if (parsed.exports.length > 0) {
|
|
1877
|
+
parts.push(`${parsed.exports.length} export${parsed.exports.length > 1 ? 's' : ''}`);
|
|
1878
|
+
}
|
|
1879
|
+
return parts.join(' | ');
|
|
1880
|
+
}
|
|
1881
|
+
extractCalls(body, language) {
|
|
1882
|
+
const calls = [];
|
|
1883
|
+
// Generic function call pattern
|
|
1884
|
+
const callPatterns = [
|
|
1885
|
+
/(\w+)\s*\(/g, // Direct calls: foo()
|
|
1886
|
+
/(\w+)\.\s*(\w+)\s*\(/g, // Method calls: obj.method()
|
|
1887
|
+
];
|
|
1888
|
+
// Language-specific patterns
|
|
1889
|
+
switch (language) {
|
|
1890
|
+
case 'python':
|
|
1891
|
+
callPatterns.push(/self\.(\w+)\s*\(/g);
|
|
1892
|
+
break;
|
|
1893
|
+
case 'go':
|
|
1894
|
+
callPatterns.push(/(\w+)\.\s*(\w+)\s*\(/g);
|
|
1895
|
+
break;
|
|
1896
|
+
case 'rust':
|
|
1897
|
+
callPatterns.push(/(\w+)::(\w+)\s*\(/g);
|
|
1898
|
+
break;
|
|
1899
|
+
}
|
|
1900
|
+
// Keywords to exclude
|
|
1901
|
+
const keywords = new Set([
|
|
1902
|
+
'if', 'else', 'for', 'while', 'switch', 'case', 'return', 'new',
|
|
1903
|
+
'typeof', 'instanceof', 'throw', 'try', 'catch', 'finally',
|
|
1904
|
+
'class', 'function', 'const', 'let', 'var', 'import', 'export',
|
|
1905
|
+
'async', 'await', 'yield', 'from', 'as', 'extends', 'implements',
|
|
1906
|
+
'interface', 'type', 'enum', 'namespace', 'module', 'require',
|
|
1907
|
+
'console', 'process', 'Math', 'JSON', 'Object', 'Array', 'String',
|
|
1908
|
+
'Number', 'Boolean', 'Promise', 'Map', 'Set', 'Error', 'self',
|
|
1909
|
+
'super', 'this', 'def', 'elif', 'except', 'lambda', 'with',
|
|
1910
|
+
'assert', 'raise', 'pass', 'break', 'continue', 'del', 'global',
|
|
1911
|
+
'print', 'len', 'range', 'str', 'int', 'float', 'list', 'dict',
|
|
1912
|
+
'tuple', 'set', 'bool', 'type', 'isinstance', 'hasattr', 'getattr',
|
|
1913
|
+
'setattr', 'iter', 'next', 'enumerate', 'zip', 'map', 'filter',
|
|
1914
|
+
'sorted', 'reversed', 'min', 'max', 'sum', 'abs', 'round',
|
|
1915
|
+
'append', 'extend', 'insert', 'remove', 'pop', 'clear',
|
|
1916
|
+
'keys', 'values', 'items', 'get', 'update',
|
|
1917
|
+
]);
|
|
1918
|
+
for (const pattern of callPatterns) {
|
|
1919
|
+
let match;
|
|
1920
|
+
while ((match = pattern.exec(body)) !== null) {
|
|
1921
|
+
const name = match[1] || match[2] || match[0];
|
|
1922
|
+
if (!keywords.has(name) && name.length > 1 && /^[a-zA-Z_]/.test(name)) {
|
|
1923
|
+
calls.push(name);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
return Array.from(new Set(calls));
|
|
1928
|
+
}
|
|
1929
|
+
symbolToOutline(sym) {
|
|
1930
|
+
return {
|
|
1931
|
+
name: sym.name,
|
|
1932
|
+
kind: sym.kind,
|
|
1933
|
+
line: sym.line,
|
|
1934
|
+
endLine: sym.endLine,
|
|
1935
|
+
icon: this.getSymbolIcon(sym.kind),
|
|
1936
|
+
children: sym.children?.map(c => this.symbolToOutline(c)) || [],
|
|
1937
|
+
signature: sym.signature,
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
getSymbolIcon(kind) {
|
|
1941
|
+
const icons = {
|
|
1942
|
+
class: '🔷',
|
|
1943
|
+
function: '⚡',
|
|
1944
|
+
method: '🔹',
|
|
1945
|
+
variable: '📌',
|
|
1946
|
+
interface: '🔌',
|
|
1947
|
+
type: '🏷️',
|
|
1948
|
+
enum: '🎯',
|
|
1949
|
+
constant: '🔒',
|
|
1950
|
+
namespace: '📦',
|
|
1951
|
+
import: '📥',
|
|
1952
|
+
};
|
|
1953
|
+
return icons[kind] || '📄';
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
//# sourceMappingURL=tree-sitter.js.map
|