claude-brain 0.30.2 → 0.30.3
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/README.md +241 -191
- package/VERSION +1 -1
- package/assets/CLAUDE-unified.md +11 -11
- package/assets/CLAUDE.md +29 -29
- package/package.json +7 -3
- package/packs/backend/node.json +173 -173
- package/packs/core/javascript.json +176 -176
- package/packs/core/typescript.json +222 -222
- package/packs/frontend/react.json +254 -254
- package/packs/meta/testing.json +172 -172
- package/scripts/postinstall.mjs +531 -531
- package/src/automation/decision-detector.ts +452 -452
- package/src/automation/phase12-manager.ts +456 -456
- package/src/automation/proactive-recall.ts +373 -373
- package/src/automation/project-detector.ts +310 -310
- package/src/automation/repo-scanner.ts +210 -205
- package/src/cli/auto-setup.ts +75 -75
- package/src/cli/auto-start.ts +266 -266
- package/src/cli/bin.ts +264 -264
- package/src/cli/commands/autostart.ts +90 -90
- package/src/cli/commands/chroma.ts +578 -577
- package/src/cli/commands/export-training.ts +70 -70
- package/src/cli/commands/export.ts +130 -130
- package/src/cli/commands/git-hook.ts +183 -183
- package/src/cli/commands/hooks.ts +217 -217
- package/src/cli/commands/init.ts +123 -123
- package/src/cli/commands/install-mcp.ts +122 -111
- package/src/cli/commands/models.ts +979 -979
- package/src/cli/commands/pack.ts +200 -200
- package/src/cli/commands/refresh.ts +344 -339
- package/src/cli/commands/reindex.ts +120 -120
- package/src/cli/commands/serve.ts +466 -463
- package/src/cli/commands/start.ts +44 -44
- package/src/cli/commands/status.ts +220 -203
- package/src/cli/commands/uninstall-mcp.ts +45 -41
- package/src/cli/commands/update.ts +130 -124
- package/src/cli/migrate-chroma.ts +106 -106
- package/src/cli/ui/animations.ts +80 -80
- package/src/cli/ui/components.ts +82 -82
- package/src/cli/ui/index.ts +4 -4
- package/src/cli/ui/logo.ts +36 -36
- package/src/cli/ui/theme.ts +55 -55
- package/src/code-intelligence/indexer.ts +352 -352
- package/src/code-intelligence/linker.ts +178 -178
- package/src/code-intelligence/parser.ts +484 -484
- package/src/code-intelligence/query.ts +291 -291
- package/src/code-intelligence/schema.ts +83 -83
- package/src/code-intelligence/types.ts +95 -95
- package/src/config/defaults.ts +52 -52
- package/src/config/home.ts +56 -56
- package/src/config/index.ts +5 -5
- package/src/config/loader.ts +192 -192
- package/src/config/schema.ts +446 -415
- package/src/config/validator.ts +182 -182
- package/src/context/assembler.ts +407 -400
- package/src/context/index.ts +79 -79
- package/src/context/progress-tracker.ts +174 -174
- package/src/context/standards-manager.ts +287 -287
- package/src/context/validator.ts +58 -58
- package/src/diagnostics/index.ts +122 -121
- package/src/health/index.ts +233 -232
- package/src/hooks/brain-hook.ts +134 -131
- package/src/hooks/capture.ts +168 -168
- package/src/hooks/claude-code-mastery.md +112 -112
- package/src/hooks/context-hook.ts +260 -245
- package/src/hooks/deduplicator.ts +72 -72
- package/src/hooks/git-capture.ts +109 -109
- package/src/hooks/git-hook-installer.ts +211 -207
- package/src/hooks/index.ts +20 -20
- package/src/hooks/installer.ts +306 -288
- package/src/hooks/interceptor-hook.ts +204 -201
- package/src/hooks/passive-classifier.ts +397 -397
- package/src/hooks/queue.ts +160 -129
- package/src/hooks/session-tracker.ts +312 -312
- package/src/hooks/types.ts +52 -52
- package/src/index.ts +7 -7
- package/src/intelligence/cross-project/generalizer.ts +283 -283
- package/src/intelligence/cross-project/index.ts +7 -7
- package/src/intelligence/hf-downloader.ts +222 -222
- package/src/intelligence/hf-manifest.json +78 -78
- package/src/intelligence/index.ts +24 -24
- package/src/intelligence/inference-router.ts +762 -762
- package/src/intelligence/model-manager.ts +263 -245
- package/src/intelligence/optimization/index.ts +10 -10
- package/src/intelligence/optimization/precompute.ts +202 -202
- package/src/intelligence/optimization/semantic-cache.ts +213 -207
- package/src/intelligence/prediction/index.ts +7 -7
- package/src/intelligence/prediction/recommender.ts +276 -268
- package/src/intelligence/reasoning/chain-retrieval.ts +243 -247
- package/src/intelligence/reasoning/index.ts +7 -7
- package/src/intelligence/temporal/evolution.ts +193 -197
- package/src/intelligence/temporal/index.ts +16 -16
- package/src/intelligence/temporal/query-processor.ts +190 -190
- package/src/intelligence/temporal/timeline.ts +272 -259
- package/src/intelligence/temporal/trends.ts +263 -263
- package/src/intelligence/tokenizer.ts +118 -118
- package/src/knowledge/entity-extractor.ts +447 -443
- package/src/knowledge/graph/builder.ts +185 -185
- package/src/knowledge/graph/linker.ts +201 -201
- package/src/knowledge/graph/memory-graph.ts +359 -359
- package/src/knowledge/graph/schema.ts +99 -99
- package/src/knowledge/graph/search.ts +166 -166
- package/src/knowledge/relationship-extractor.ts +108 -108
- package/src/memory/chroma/client.ts +211 -192
- package/src/memory/chroma/collection-manager.ts +92 -92
- package/src/memory/chroma/config.ts +57 -57
- package/src/memory/chroma/embeddings.ts +177 -175
- package/src/memory/chroma/index.ts +82 -82
- package/src/memory/chroma/migration.ts +270 -270
- package/src/memory/chroma/schemas.ts +69 -69
- package/src/memory/chroma/search.ts +319 -315
- package/src/memory/chroma/store.ts +755 -747
- package/src/memory/compression.ts +121 -121
- package/src/memory/consolidation/archiver.ts +162 -165
- package/src/memory/consolidation/merger.ts +182 -186
- package/src/memory/consolidation/scorer.ts +136 -136
- package/src/memory/database.ts +9 -0
- package/src/memory/dual-write.ts +145 -0
- package/src/memory/embeddings.ts +226 -226
- package/src/memory/episodic/detector.ts +108 -108
- package/src/memory/episodic/manager.ts +347 -351
- package/src/memory/episodic/summarizer.ts +179 -179
- package/src/memory/episodic/types.ts +52 -52
- package/src/memory/fts5-search.ts +692 -633
- package/src/memory/index.ts +943 -1060
- package/src/memory/migrations/add-fts5.ts +118 -108
- package/src/memory/patterns.ts +438 -438
- package/src/memory/pruning.ts +60 -60
- package/src/memory/schema.ts +88 -88
- package/src/memory/store.ts +911 -787
- package/src/orchestrator/handlers/decision-handler.ts +204 -204
- package/src/packs/index.ts +9 -9
- package/src/packs/loader.ts +134 -134
- package/src/packs/manager.ts +204 -204
- package/src/packs/ranker.ts +78 -78
- package/src/packs/types.ts +81 -81
- package/src/phase12/index.ts +5 -5
- package/src/retrieval/bm25/index.ts +300 -297
- package/src/retrieval/bm25/tokenizer.ts +184 -184
- package/src/retrieval/feedback/adaptive.ts +221 -221
- package/src/retrieval/feedback/index.ts +16 -16
- package/src/retrieval/feedback/metrics.ts +221 -221
- package/src/retrieval/feedback/store.ts +283 -283
- package/src/retrieval/fusion/index.ts +194 -194
- package/src/retrieval/fusion/rrf.ts +165 -165
- package/src/retrieval/index.ts +12 -12
- package/src/retrieval/pipeline.ts +375 -375
- package/src/retrieval/query/expander.ts +203 -203
- package/src/retrieval/query/index.ts +27 -27
- package/src/retrieval/query/intent-classifier.ts +252 -252
- package/src/retrieval/query/temporal-parser.ts +295 -295
- package/src/retrieval/reranker/index.ts +189 -188
- package/src/retrieval/reranker/model.ts +99 -95
- package/src/retrieval/service.ts +125 -125
- package/src/retrieval/types.ts +162 -162
- package/src/routing/entity-extractor.ts +454 -454
- package/src/routing/handlers/exploration-handler.ts +369 -0
- package/src/routing/handlers/index.ts +19 -0
- package/src/routing/handlers/memory-handler.ts +273 -0
- package/src/routing/handlers/mutation-handler.ts +241 -0
- package/src/routing/handlers/recall-handler.ts +642 -0
- package/src/routing/handlers/shared.ts +515 -0
- package/src/routing/handlers/types.ts +48 -0
- package/src/routing/intent-classifier.ts +552 -552
- package/src/routing/response-filter.ts +399 -391
- package/src/routing/router.ts +245 -2193
- package/src/routing/search-engine.ts +521 -514
- package/src/routing/types.ts +104 -94
- package/src/scripts/health-check.ts +118 -118
- package/src/scripts/setup.ts +122 -122
- package/src/server/auto-updater.ts +283 -276
- package/src/server/handlers/call-tool.ts +159 -159
- package/src/server/handlers/list-tools.ts +35 -35
- package/src/server/handlers/tools/auto-remember.ts +165 -165
- package/src/server/handlers/tools/brain.ts +86 -86
- package/src/server/handlers/tools/create-project.ts +135 -135
- package/src/server/handlers/tools/get-code-standards.ts +123 -123
- package/src/server/handlers/tools/get-corrections.ts +152 -152
- package/src/server/handlers/tools/get-patterns.ts +156 -156
- package/src/server/handlers/tools/get-project-context.ts +75 -75
- package/src/server/handlers/tools/index.ts +30 -30
- package/src/server/handlers/tools/init-project.ts +756 -756
- package/src/server/handlers/tools/list-projects.ts +126 -126
- package/src/server/handlers/tools/recall-similar.ts +87 -87
- package/src/server/handlers/tools/recognize-pattern.ts +132 -132
- package/src/server/handlers/tools/record-correction.ts +131 -131
- package/src/server/handlers/tools/remember-decision.ts +168 -168
- package/src/server/handlers/tools/schemas.ts +179 -179
- package/src/server/handlers/tools/search-code.ts +122 -122
- package/src/server/handlers/tools/smart-context.ts +146 -146
- package/src/server/handlers/tools/update-progress.ts +131 -131
- package/src/server/http-api.ts +215 -1229
- package/src/server/mcp-proxy.ts +85 -84
- package/src/server/mcp-server.ts +285 -284
- package/src/server/middleware/auth.ts +39 -0
- package/src/server/middleware/error-handler.ts +37 -0
- package/src/server/middleware/rate-limit.ts +53 -0
- package/src/server/middleware/validate.ts +42 -0
- package/src/server/pid-manager.ts +137 -136
- package/src/server/providers/resources.ts +581 -581
- package/src/server/routes/code.ts +228 -0
- package/src/server/routes/context.ts +26 -0
- package/src/server/routes/health.ts +19 -0
- package/src/server/routes/helpers.ts +100 -0
- package/src/server/routes/hooks.ts +197 -0
- package/src/server/routes/mcp.ts +47 -0
- package/src/server/routes/memory.ts +397 -0
- package/src/server/routes/models.ts +96 -0
- package/src/server/routes/projects.ts +89 -0
- package/src/server/routes/types.ts +21 -0
- package/src/server/schemas/api-schemas.ts +202 -0
- package/src/server/services.ts +720 -720
- package/src/server/utils/memory-indicator.ts +84 -84
- package/src/server/utils/response-formatter.ts +129 -129
- package/src/server/web-viewer.ts +1145 -1115
- package/src/setup/index.ts +38 -38
- package/src/tools/registry.ts +115 -115
- package/src/tools/schemas.ts +666 -666
- package/src/tools/types.ts +412 -412
- package/src/training/data-store.ts +320 -298
- package/src/training/retrain-pipeline.ts +399 -394
- package/src/utils/error-handler.ts +136 -136
- package/src/utils/index.ts +58 -58
- package/src/utils/kill-port.ts +55 -53
- package/src/utils/phase12-helper.ts +56 -56
- package/src/utils/safe-path.ts +43 -0
- package/src/utils/timing.ts +47 -47
- package/src/utils/transaction.ts +63 -63
- package/src/vault/index.ts +4 -3
- package/src/vault/paths.ts +106 -106
- package/src/vault/query.ts +4 -1
- package/src/vault/reader.ts +44 -1
- package/src/vault/watcher.ts +24 -1
- package/src/vault/writer.ts +487 -413
- package/skills/persistent-memory/SKILL.md +0 -148
- package/skills/persistent-memory/references/tool-reference.md +0 -90
|
@@ -1,484 +1,484 @@
|
|
|
1
|
-
import Parser from 'web-tree-sitter'
|
|
2
|
-
import { join, extname } from 'node:path'
|
|
3
|
-
import type { Language, CodeSymbol, ParsedFile, ImportInfo } from './types'
|
|
4
|
-
|
|
5
|
-
/** Map file extensions to Language */
|
|
6
|
-
const EXTENSION_MAP: Record<string, Language> = {
|
|
7
|
-
'.ts': 'typescript',
|
|
8
|
-
'.tsx': 'tsx',
|
|
9
|
-
'.js': 'javascript',
|
|
10
|
-
'.jsx': 'jsx',
|
|
11
|
-
'.mjs': 'javascript',
|
|
12
|
-
'.cjs': 'javascript',
|
|
13
|
-
'.py': 'python',
|
|
14
|
-
'.go': 'go',
|
|
15
|
-
'.rs': 'rust',
|
|
16
|
-
'.vue': 'vue',
|
|
17
|
-
'.html': 'html',
|
|
18
|
-
'.css': 'css',
|
|
19
|
-
'.json': 'json',
|
|
20
|
-
'.yaml': 'yaml',
|
|
21
|
-
'.yml': 'yaml',
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Languages that share the TypeScript grammar */
|
|
25
|
-
const TS_FAMILY = new Set<Language>(['typescript', 'javascript'])
|
|
26
|
-
|
|
27
|
-
/** Languages that use the TSX grammar */
|
|
28
|
-
const TSX_FAMILY = new Set<Language>(['tsx', 'jsx'])
|
|
29
|
-
|
|
30
|
-
/** Map Language to WASM grammar file name */
|
|
31
|
-
function grammarNameForLanguage(lang: Language): string | null {
|
|
32
|
-
if (TS_FAMILY.has(lang)) return 'typescript'
|
|
33
|
-
if (TSX_FAMILY.has(lang)) return 'tsx'
|
|
34
|
-
return null
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Tree-sitter based code parser.
|
|
39
|
-
* Currently supports TypeScript, JavaScript, TSX, and JSX.
|
|
40
|
-
* Call `initialize()` once before using `parseFile()`.
|
|
41
|
-
*/
|
|
42
|
-
export class CodeParser {
|
|
43
|
-
private parser: Parser | null = null
|
|
44
|
-
private languages = new Map<string, Parser.Language>()
|
|
45
|
-
private initialized = false
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Initialize the parser and load WASM grammars.
|
|
49
|
-
* Must be called once before parseFile().
|
|
50
|
-
*/
|
|
51
|
-
async initialize(): Promise<void> {
|
|
52
|
-
if (this.initialized) return
|
|
53
|
-
|
|
54
|
-
await Parser.init()
|
|
55
|
-
this.parser = new Parser()
|
|
56
|
-
|
|
57
|
-
const grammarsToLoad = ['typescript', 'tsx']
|
|
58
|
-
for (const name of grammarsToLoad) {
|
|
59
|
-
const wasmPath = this.resolveWasmPath(name)
|
|
60
|
-
try {
|
|
61
|
-
const lang = await Parser.Language.load(wasmPath)
|
|
62
|
-
this.languages.set(name, lang)
|
|
63
|
-
} catch (err) {
|
|
64
|
-
throw new Error(
|
|
65
|
-
`Failed to load tree-sitter WASM grammar "${name}" from ${wasmPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
66
|
-
)
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
this.initialized = true
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Detect language from a file path's extension.
|
|
75
|
-
* Returns null for unsupported file types.
|
|
76
|
-
*/
|
|
77
|
-
detectLanguage(filePath: string): Language | null {
|
|
78
|
-
const ext = extname(filePath).toLowerCase()
|
|
79
|
-
return EXTENSION_MAP[ext] ?? null
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Parse a file and extract its symbols and imports.
|
|
84
|
-
* Returns an empty symbol list for unsupported languages.
|
|
85
|
-
*/
|
|
86
|
-
async parseFile(filePath: string, content: string): Promise<ParsedFile> {
|
|
87
|
-
const language = this.detectLanguage(filePath)
|
|
88
|
-
if (!language) {
|
|
89
|
-
return {
|
|
90
|
-
filePath,
|
|
91
|
-
language: 'typescript',
|
|
92
|
-
symbols: [],
|
|
93
|
-
imports: [],
|
|
94
|
-
lineCount: content.split('\n').length,
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const grammarName = grammarNameForLanguage(language)
|
|
99
|
-
if (!grammarName) {
|
|
100
|
-
return {
|
|
101
|
-
filePath,
|
|
102
|
-
language,
|
|
103
|
-
symbols: [],
|
|
104
|
-
imports: [],
|
|
105
|
-
lineCount: content.split('\n').length,
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (!this.initialized || !this.parser) {
|
|
110
|
-
throw new Error('CodeParser not initialized. Call initialize() first.')
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const treeSitterLang = this.languages.get(grammarName)
|
|
114
|
-
if (!treeSitterLang) {
|
|
115
|
-
throw new Error(`Grammar "${grammarName}" not loaded.`)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
this.parser.setLanguage(treeSitterLang)
|
|
119
|
-
|
|
120
|
-
let tree: Parser.Tree | undefined
|
|
121
|
-
try {
|
|
122
|
-
tree = this.parser.parse(content)
|
|
123
|
-
|
|
124
|
-
const symbols = this.extractSymbols(tree, filePath)
|
|
125
|
-
const imports = this.extractImports(tree)
|
|
126
|
-
|
|
127
|
-
return {
|
|
128
|
-
filePath,
|
|
129
|
-
language,
|
|
130
|
-
symbols,
|
|
131
|
-
imports,
|
|
132
|
-
lineCount: content.split('\n').length,
|
|
133
|
-
}
|
|
134
|
-
} finally {
|
|
135
|
-
tree?.delete()
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Extract symbols (functions, classes, interfaces, etc.) from a parsed tree.
|
|
141
|
-
*/
|
|
142
|
-
private extractSymbols(tree: Parser.Tree, filePath: string): CodeSymbol[] {
|
|
143
|
-
const symbols: CodeSymbol[] = []
|
|
144
|
-
this.walkForSymbols(tree.rootNode, filePath, null, false, symbols)
|
|
145
|
-
return symbols
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Recursively walk the AST to find symbol declarations.
|
|
150
|
-
*/
|
|
151
|
-
private walkForSymbols(
|
|
152
|
-
node: Parser.SyntaxNode,
|
|
153
|
-
filePath: string,
|
|
154
|
-
parentSymbol: string | null,
|
|
155
|
-
isExported: boolean,
|
|
156
|
-
symbols: CodeSymbol[]
|
|
157
|
-
): void {
|
|
158
|
-
const type = node.type
|
|
159
|
-
|
|
160
|
-
if (type === 'export_statement') {
|
|
161
|
-
for (const child of node.namedChildren) {
|
|
162
|
-
this.walkForSymbols(child, filePath, parentSymbol, true, symbols)
|
|
163
|
-
}
|
|
164
|
-
return
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const symbol = this.nodeToSymbol(node, filePath, parentSymbol, isExported)
|
|
168
|
-
if (symbol) {
|
|
169
|
-
symbols.push(symbol)
|
|
170
|
-
|
|
171
|
-
// For classes/interfaces, walk children to find methods
|
|
172
|
-
if (symbol.type === 'class' || symbol.type === 'interface') {
|
|
173
|
-
const body = node.childForFieldName('body')
|
|
174
|
-
if (body) {
|
|
175
|
-
for (const child of body.namedChildren) {
|
|
176
|
-
this.walkForSymbols(child, filePath, symbol.name, isExported, symbols)
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
return
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Continue walking for top-level containers
|
|
184
|
-
if (this.isContainerNode(type)) {
|
|
185
|
-
for (const child of node.namedChildren) {
|
|
186
|
-
this.walkForSymbols(child, filePath, parentSymbol, false, symbols)
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Convert a tree-sitter node to a CodeSymbol if it represents a declaration.
|
|
193
|
-
*/
|
|
194
|
-
private nodeToSymbol(
|
|
195
|
-
node: Parser.SyntaxNode,
|
|
196
|
-
filePath: string,
|
|
197
|
-
parentSymbol: string | null,
|
|
198
|
-
isExported: boolean
|
|
199
|
-
): CodeSymbol | null {
|
|
200
|
-
const type = node.type
|
|
201
|
-
|
|
202
|
-
switch (type) {
|
|
203
|
-
case 'function_declaration':
|
|
204
|
-
case 'function_signature': {
|
|
205
|
-
const name = node.childForFieldName('name')
|
|
206
|
-
if (!name) return null
|
|
207
|
-
return {
|
|
208
|
-
name: name.text,
|
|
209
|
-
type: 'function',
|
|
210
|
-
filePath,
|
|
211
|
-
lineStart: node.startPosition.row + 1,
|
|
212
|
-
lineEnd: node.endPosition.row + 1,
|
|
213
|
-
parentSymbol: parentSymbol ?? undefined,
|
|
214
|
-
signature: this.extractSignature(node),
|
|
215
|
-
exported: isExported,
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
case 'class_declaration': {
|
|
220
|
-
const name = node.childForFieldName('name')
|
|
221
|
-
if (!name) return null
|
|
222
|
-
return {
|
|
223
|
-
name: name.text,
|
|
224
|
-
type: 'class',
|
|
225
|
-
filePath,
|
|
226
|
-
lineStart: node.startPosition.row + 1,
|
|
227
|
-
lineEnd: node.endPosition.row + 1,
|
|
228
|
-
parentSymbol: parentSymbol ?? undefined,
|
|
229
|
-
exported: isExported,
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
case 'interface_declaration': {
|
|
234
|
-
const name = node.childForFieldName('name')
|
|
235
|
-
if (!name) return null
|
|
236
|
-
return {
|
|
237
|
-
name: name.text,
|
|
238
|
-
type: 'interface',
|
|
239
|
-
filePath,
|
|
240
|
-
lineStart: node.startPosition.row + 1,
|
|
241
|
-
lineEnd: node.endPosition.row + 1,
|
|
242
|
-
parentSymbol: parentSymbol ?? undefined,
|
|
243
|
-
exported: isExported,
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
case 'type_alias_declaration': {
|
|
248
|
-
const name = node.childForFieldName('name')
|
|
249
|
-
if (!name) return null
|
|
250
|
-
return {
|
|
251
|
-
name: name.text,
|
|
252
|
-
type: 'type',
|
|
253
|
-
filePath,
|
|
254
|
-
lineStart: node.startPosition.row + 1,
|
|
255
|
-
lineEnd: node.endPosition.row + 1,
|
|
256
|
-
parentSymbol: parentSymbol ?? undefined,
|
|
257
|
-
exported: isExported,
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
case 'enum_declaration': {
|
|
262
|
-
const name = node.childForFieldName('name')
|
|
263
|
-
if (!name) return null
|
|
264
|
-
return {
|
|
265
|
-
name: name.text,
|
|
266
|
-
type: 'enum',
|
|
267
|
-
filePath,
|
|
268
|
-
lineStart: node.startPosition.row + 1,
|
|
269
|
-
lineEnd: node.endPosition.row + 1,
|
|
270
|
-
parentSymbol: parentSymbol ?? undefined,
|
|
271
|
-
exported: isExported,
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
case 'method_definition':
|
|
276
|
-
case 'method_signature': {
|
|
277
|
-
const name = node.childForFieldName('name')
|
|
278
|
-
if (!name) return null
|
|
279
|
-
return {
|
|
280
|
-
name: name.text,
|
|
281
|
-
type: 'method',
|
|
282
|
-
filePath,
|
|
283
|
-
lineStart: node.startPosition.row + 1,
|
|
284
|
-
lineEnd: node.endPosition.row + 1,
|
|
285
|
-
parentSymbol: parentSymbol ?? undefined,
|
|
286
|
-
signature: this.extractSignature(node),
|
|
287
|
-
exported: isExported,
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
case 'public_field_definition':
|
|
292
|
-
case 'property_signature': {
|
|
293
|
-
const name = node.childForFieldName('name')
|
|
294
|
-
if (!name) return null
|
|
295
|
-
return {
|
|
296
|
-
name: name.text,
|
|
297
|
-
type: 'variable',
|
|
298
|
-
filePath,
|
|
299
|
-
lineStart: node.startPosition.row + 1,
|
|
300
|
-
lineEnd: node.endPosition.row + 1,
|
|
301
|
-
parentSymbol: parentSymbol ?? undefined,
|
|
302
|
-
exported: isExported,
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
case 'lexical_declaration':
|
|
307
|
-
case 'variable_declaration': {
|
|
308
|
-
return this.extractFromLexicalDeclaration(node, filePath, parentSymbol, isExported)
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
default:
|
|
312
|
-
return null
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Extract a symbol from a lexical_declaration (const/let) or variable_declaration (var).
|
|
318
|
-
* Handles arrow functions assigned to variables.
|
|
319
|
-
*/
|
|
320
|
-
private extractFromLexicalDeclaration(
|
|
321
|
-
node: Parser.SyntaxNode,
|
|
322
|
-
filePath: string,
|
|
323
|
-
parentSymbol: string | null,
|
|
324
|
-
isExported: boolean
|
|
325
|
-
): CodeSymbol | null {
|
|
326
|
-
const declarator = node.namedChildren.find(
|
|
327
|
-
(c) => c.type === 'variable_declarator'
|
|
328
|
-
)
|
|
329
|
-
if (!declarator) return null
|
|
330
|
-
|
|
331
|
-
const name = declarator.childForFieldName('name')
|
|
332
|
-
if (!name) return null
|
|
333
|
-
|
|
334
|
-
const value = declarator.childForFieldName('value')
|
|
335
|
-
const isConst = node.text.trimStart().startsWith('const')
|
|
336
|
-
|
|
337
|
-
if (value && (value.type === 'arrow_function' || value.type === 'function_expression' || value.type === 'function')) {
|
|
338
|
-
return {
|
|
339
|
-
name: name.text,
|
|
340
|
-
type: 'function',
|
|
341
|
-
filePath,
|
|
342
|
-
lineStart: node.startPosition.row + 1,
|
|
343
|
-
lineEnd: node.endPosition.row + 1,
|
|
344
|
-
parentSymbol: parentSymbol ?? undefined,
|
|
345
|
-
signature: this.extractSignature(value),
|
|
346
|
-
exported: isExported,
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return {
|
|
351
|
-
name: name.text,
|
|
352
|
-
type: isConst ? 'constant' : 'variable',
|
|
353
|
-
filePath,
|
|
354
|
-
lineStart: node.startPosition.row + 1,
|
|
355
|
-
lineEnd: node.endPosition.row + 1,
|
|
356
|
-
parentSymbol: parentSymbol ?? undefined,
|
|
357
|
-
exported: isExported,
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* Extract function signature (parameters + return type).
|
|
363
|
-
*/
|
|
364
|
-
private extractSignature(node: Parser.SyntaxNode): string | undefined {
|
|
365
|
-
const params = node.childForFieldName('parameters')
|
|
366
|
-
const returnType = node.childForFieldName('return_type')
|
|
367
|
-
|
|
368
|
-
if (!params) return undefined
|
|
369
|
-
|
|
370
|
-
let sig = params.text
|
|
371
|
-
if (returnType) {
|
|
372
|
-
// returnType.text may include the leading `:`, so avoid doubling it
|
|
373
|
-
const rt = returnType.text.trimStart()
|
|
374
|
-
sig += rt.startsWith(':') ? ' ' + rt : ': ' + rt
|
|
375
|
-
}
|
|
376
|
-
return sig
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Extract import statements from the AST.
|
|
381
|
-
*/
|
|
382
|
-
private extractImports(tree: Parser.Tree): ImportInfo[] {
|
|
383
|
-
const imports: ImportInfo[] = []
|
|
384
|
-
|
|
385
|
-
for (const child of tree.rootNode.namedChildren) {
|
|
386
|
-
if (child.type === 'import_statement') {
|
|
387
|
-
const info = this.parseImportStatement(child)
|
|
388
|
-
if (info) imports.push(info)
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
return imports
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* Parse a single import_statement node into an ImportInfo.
|
|
397
|
-
*/
|
|
398
|
-
private parseImportStatement(node: Parser.SyntaxNode): ImportInfo | null {
|
|
399
|
-
const source = node.childForFieldName('source')
|
|
400
|
-
if (!source) return null
|
|
401
|
-
|
|
402
|
-
const sourcePath = source.text.replace(/^['"]|['"]$/g, '')
|
|
403
|
-
const names: string[] = []
|
|
404
|
-
|
|
405
|
-
for (const child of node.namedChildren) {
|
|
406
|
-
if (child.type === 'import_clause') {
|
|
407
|
-
this.collectImportNames(child, names)
|
|
408
|
-
}
|
|
409
|
-
if (child.type === 'namespace_import') {
|
|
410
|
-
const alias = child.childForFieldName('name') ?? child.namedChildren[0]
|
|
411
|
-
if (alias) names.push(alias.text)
|
|
412
|
-
}
|
|
413
|
-
if (child.type === 'named_imports') {
|
|
414
|
-
for (const spec of child.namedChildren) {
|
|
415
|
-
if (spec.type === 'import_specifier') {
|
|
416
|
-
const specName = spec.childForFieldName('name') ?? spec.namedChildren[0]
|
|
417
|
-
if (specName) names.push(specName.text)
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
return {
|
|
424
|
-
source: sourcePath,
|
|
425
|
-
names,
|
|
426
|
-
importType: 'import',
|
|
427
|
-
line: node.startPosition.row + 1,
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* Collect imported names from an import_clause node.
|
|
433
|
-
*/
|
|
434
|
-
private collectImportNames(node: Parser.SyntaxNode, names: string[]): void {
|
|
435
|
-
for (const child of node.namedChildren) {
|
|
436
|
-
switch (child.type) {
|
|
437
|
-
case 'identifier':
|
|
438
|
-
names.push(child.text)
|
|
439
|
-
break
|
|
440
|
-
case 'named_imports':
|
|
441
|
-
for (const spec of child.namedChildren) {
|
|
442
|
-
if (spec.type === 'import_specifier') {
|
|
443
|
-
const specName = spec.childForFieldName('name') ?? spec.namedChildren[0]
|
|
444
|
-
if (specName) names.push(specName.text)
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
break
|
|
448
|
-
case 'namespace_import': {
|
|
449
|
-
const alias = child.childForFieldName('name') ?? child.namedChildren[0]
|
|
450
|
-
if (alias) names.push(alias.text)
|
|
451
|
-
break
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
/**
|
|
458
|
-
* Check if a node type is a container that may hold declarations.
|
|
459
|
-
*/
|
|
460
|
-
private isContainerNode(type: string): boolean {
|
|
461
|
-
return (
|
|
462
|
-
type === 'program' ||
|
|
463
|
-
type === 'statement_block' ||
|
|
464
|
-
type === 'module' ||
|
|
465
|
-
type === 'export_statement'
|
|
466
|
-
)
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
/**
|
|
470
|
-
* Resolve the path to a tree-sitter WASM grammar file.
|
|
471
|
-
*/
|
|
472
|
-
private resolveWasmPath(grammarName: string): string {
|
|
473
|
-
try {
|
|
474
|
-
const wasmsDir = require.resolve('tree-sitter-wasms/package.json')
|
|
475
|
-
return join(wasmsDir, '..', 'out', `tree-sitter-${grammarName}.wasm`)
|
|
476
|
-
} catch {
|
|
477
|
-
return join(
|
|
478
|
-
import.meta.dir,
|
|
479
|
-
'../../node_modules/tree-sitter-wasms/out',
|
|
480
|
-
`tree-sitter-${grammarName}.wasm`
|
|
481
|
-
)
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
}
|
|
1
|
+
import Parser from 'web-tree-sitter'
|
|
2
|
+
import { join, extname } from 'node:path'
|
|
3
|
+
import type { Language, CodeSymbol, ParsedFile, ImportInfo } from './types'
|
|
4
|
+
|
|
5
|
+
/** Map file extensions to Language */
|
|
6
|
+
const EXTENSION_MAP: Record<string, Language> = {
|
|
7
|
+
'.ts': 'typescript',
|
|
8
|
+
'.tsx': 'tsx',
|
|
9
|
+
'.js': 'javascript',
|
|
10
|
+
'.jsx': 'jsx',
|
|
11
|
+
'.mjs': 'javascript',
|
|
12
|
+
'.cjs': 'javascript',
|
|
13
|
+
'.py': 'python',
|
|
14
|
+
'.go': 'go',
|
|
15
|
+
'.rs': 'rust',
|
|
16
|
+
'.vue': 'vue',
|
|
17
|
+
'.html': 'html',
|
|
18
|
+
'.css': 'css',
|
|
19
|
+
'.json': 'json',
|
|
20
|
+
'.yaml': 'yaml',
|
|
21
|
+
'.yml': 'yaml',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Languages that share the TypeScript grammar */
|
|
25
|
+
const TS_FAMILY = new Set<Language>(['typescript', 'javascript'])
|
|
26
|
+
|
|
27
|
+
/** Languages that use the TSX grammar */
|
|
28
|
+
const TSX_FAMILY = new Set<Language>(['tsx', 'jsx'])
|
|
29
|
+
|
|
30
|
+
/** Map Language to WASM grammar file name */
|
|
31
|
+
function grammarNameForLanguage(lang: Language): string | null {
|
|
32
|
+
if (TS_FAMILY.has(lang)) return 'typescript'
|
|
33
|
+
if (TSX_FAMILY.has(lang)) return 'tsx'
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Tree-sitter based code parser.
|
|
39
|
+
* Currently supports TypeScript, JavaScript, TSX, and JSX.
|
|
40
|
+
* Call `initialize()` once before using `parseFile()`.
|
|
41
|
+
*/
|
|
42
|
+
export class CodeParser {
|
|
43
|
+
private parser: Parser | null = null
|
|
44
|
+
private languages = new Map<string, Parser.Language>()
|
|
45
|
+
private initialized = false
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Initialize the parser and load WASM grammars.
|
|
49
|
+
* Must be called once before parseFile().
|
|
50
|
+
*/
|
|
51
|
+
async initialize(): Promise<void> {
|
|
52
|
+
if (this.initialized) return
|
|
53
|
+
|
|
54
|
+
await Parser.init()
|
|
55
|
+
this.parser = new Parser()
|
|
56
|
+
|
|
57
|
+
const grammarsToLoad = ['typescript', 'tsx']
|
|
58
|
+
for (const name of grammarsToLoad) {
|
|
59
|
+
const wasmPath = this.resolveWasmPath(name)
|
|
60
|
+
try {
|
|
61
|
+
const lang = await Parser.Language.load(wasmPath)
|
|
62
|
+
this.languages.set(name, lang)
|
|
63
|
+
} catch (err) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`Failed to load tree-sitter WASM grammar "${name}" from ${wasmPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.initialized = true
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Detect language from a file path's extension.
|
|
75
|
+
* Returns null for unsupported file types.
|
|
76
|
+
*/
|
|
77
|
+
detectLanguage(filePath: string): Language | null {
|
|
78
|
+
const ext = extname(filePath).toLowerCase()
|
|
79
|
+
return EXTENSION_MAP[ext] ?? null
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Parse a file and extract its symbols and imports.
|
|
84
|
+
* Returns an empty symbol list for unsupported languages.
|
|
85
|
+
*/
|
|
86
|
+
async parseFile(filePath: string, content: string): Promise<ParsedFile> {
|
|
87
|
+
const language = this.detectLanguage(filePath)
|
|
88
|
+
if (!language) {
|
|
89
|
+
return {
|
|
90
|
+
filePath,
|
|
91
|
+
language: 'typescript',
|
|
92
|
+
symbols: [],
|
|
93
|
+
imports: [],
|
|
94
|
+
lineCount: content.split('\n').length,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const grammarName = grammarNameForLanguage(language)
|
|
99
|
+
if (!grammarName) {
|
|
100
|
+
return {
|
|
101
|
+
filePath,
|
|
102
|
+
language,
|
|
103
|
+
symbols: [],
|
|
104
|
+
imports: [],
|
|
105
|
+
lineCount: content.split('\n').length,
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!this.initialized || !this.parser) {
|
|
110
|
+
throw new Error('CodeParser not initialized. Call initialize() first.')
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const treeSitterLang = this.languages.get(grammarName)
|
|
114
|
+
if (!treeSitterLang) {
|
|
115
|
+
throw new Error(`Grammar "${grammarName}" not loaded.`)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.parser.setLanguage(treeSitterLang)
|
|
119
|
+
|
|
120
|
+
let tree: Parser.Tree | undefined
|
|
121
|
+
try {
|
|
122
|
+
tree = this.parser.parse(content)
|
|
123
|
+
|
|
124
|
+
const symbols = this.extractSymbols(tree, filePath)
|
|
125
|
+
const imports = this.extractImports(tree)
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
filePath,
|
|
129
|
+
language,
|
|
130
|
+
symbols,
|
|
131
|
+
imports,
|
|
132
|
+
lineCount: content.split('\n').length,
|
|
133
|
+
}
|
|
134
|
+
} finally {
|
|
135
|
+
tree?.delete()
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Extract symbols (functions, classes, interfaces, etc.) from a parsed tree.
|
|
141
|
+
*/
|
|
142
|
+
private extractSymbols(tree: Parser.Tree, filePath: string): CodeSymbol[] {
|
|
143
|
+
const symbols: CodeSymbol[] = []
|
|
144
|
+
this.walkForSymbols(tree.rootNode, filePath, null, false, symbols)
|
|
145
|
+
return symbols
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Recursively walk the AST to find symbol declarations.
|
|
150
|
+
*/
|
|
151
|
+
private walkForSymbols(
|
|
152
|
+
node: Parser.SyntaxNode,
|
|
153
|
+
filePath: string,
|
|
154
|
+
parentSymbol: string | null,
|
|
155
|
+
isExported: boolean,
|
|
156
|
+
symbols: CodeSymbol[]
|
|
157
|
+
): void {
|
|
158
|
+
const type = node.type
|
|
159
|
+
|
|
160
|
+
if (type === 'export_statement') {
|
|
161
|
+
for (const child of node.namedChildren) {
|
|
162
|
+
this.walkForSymbols(child, filePath, parentSymbol, true, symbols)
|
|
163
|
+
}
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const symbol = this.nodeToSymbol(node, filePath, parentSymbol, isExported)
|
|
168
|
+
if (symbol) {
|
|
169
|
+
symbols.push(symbol)
|
|
170
|
+
|
|
171
|
+
// For classes/interfaces, walk children to find methods
|
|
172
|
+
if (symbol.type === 'class' || symbol.type === 'interface') {
|
|
173
|
+
const body = node.childForFieldName('body')
|
|
174
|
+
if (body) {
|
|
175
|
+
for (const child of body.namedChildren) {
|
|
176
|
+
this.walkForSymbols(child, filePath, symbol.name, isExported, symbols)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Continue walking for top-level containers
|
|
184
|
+
if (this.isContainerNode(type)) {
|
|
185
|
+
for (const child of node.namedChildren) {
|
|
186
|
+
this.walkForSymbols(child, filePath, parentSymbol, false, symbols)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Convert a tree-sitter node to a CodeSymbol if it represents a declaration.
|
|
193
|
+
*/
|
|
194
|
+
private nodeToSymbol(
|
|
195
|
+
node: Parser.SyntaxNode,
|
|
196
|
+
filePath: string,
|
|
197
|
+
parentSymbol: string | null,
|
|
198
|
+
isExported: boolean
|
|
199
|
+
): CodeSymbol | null {
|
|
200
|
+
const type = node.type
|
|
201
|
+
|
|
202
|
+
switch (type) {
|
|
203
|
+
case 'function_declaration':
|
|
204
|
+
case 'function_signature': {
|
|
205
|
+
const name = node.childForFieldName('name')
|
|
206
|
+
if (!name) return null
|
|
207
|
+
return {
|
|
208
|
+
name: name.text,
|
|
209
|
+
type: 'function',
|
|
210
|
+
filePath,
|
|
211
|
+
lineStart: node.startPosition.row + 1,
|
|
212
|
+
lineEnd: node.endPosition.row + 1,
|
|
213
|
+
parentSymbol: parentSymbol ?? undefined,
|
|
214
|
+
signature: this.extractSignature(node),
|
|
215
|
+
exported: isExported,
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
case 'class_declaration': {
|
|
220
|
+
const name = node.childForFieldName('name')
|
|
221
|
+
if (!name) return null
|
|
222
|
+
return {
|
|
223
|
+
name: name.text,
|
|
224
|
+
type: 'class',
|
|
225
|
+
filePath,
|
|
226
|
+
lineStart: node.startPosition.row + 1,
|
|
227
|
+
lineEnd: node.endPosition.row + 1,
|
|
228
|
+
parentSymbol: parentSymbol ?? undefined,
|
|
229
|
+
exported: isExported,
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
case 'interface_declaration': {
|
|
234
|
+
const name = node.childForFieldName('name')
|
|
235
|
+
if (!name) return null
|
|
236
|
+
return {
|
|
237
|
+
name: name.text,
|
|
238
|
+
type: 'interface',
|
|
239
|
+
filePath,
|
|
240
|
+
lineStart: node.startPosition.row + 1,
|
|
241
|
+
lineEnd: node.endPosition.row + 1,
|
|
242
|
+
parentSymbol: parentSymbol ?? undefined,
|
|
243
|
+
exported: isExported,
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
case 'type_alias_declaration': {
|
|
248
|
+
const name = node.childForFieldName('name')
|
|
249
|
+
if (!name) return null
|
|
250
|
+
return {
|
|
251
|
+
name: name.text,
|
|
252
|
+
type: 'type',
|
|
253
|
+
filePath,
|
|
254
|
+
lineStart: node.startPosition.row + 1,
|
|
255
|
+
lineEnd: node.endPosition.row + 1,
|
|
256
|
+
parentSymbol: parentSymbol ?? undefined,
|
|
257
|
+
exported: isExported,
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
case 'enum_declaration': {
|
|
262
|
+
const name = node.childForFieldName('name')
|
|
263
|
+
if (!name) return null
|
|
264
|
+
return {
|
|
265
|
+
name: name.text,
|
|
266
|
+
type: 'enum',
|
|
267
|
+
filePath,
|
|
268
|
+
lineStart: node.startPosition.row + 1,
|
|
269
|
+
lineEnd: node.endPosition.row + 1,
|
|
270
|
+
parentSymbol: parentSymbol ?? undefined,
|
|
271
|
+
exported: isExported,
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
case 'method_definition':
|
|
276
|
+
case 'method_signature': {
|
|
277
|
+
const name = node.childForFieldName('name')
|
|
278
|
+
if (!name) return null
|
|
279
|
+
return {
|
|
280
|
+
name: name.text,
|
|
281
|
+
type: 'method',
|
|
282
|
+
filePath,
|
|
283
|
+
lineStart: node.startPosition.row + 1,
|
|
284
|
+
lineEnd: node.endPosition.row + 1,
|
|
285
|
+
parentSymbol: parentSymbol ?? undefined,
|
|
286
|
+
signature: this.extractSignature(node),
|
|
287
|
+
exported: isExported,
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
case 'public_field_definition':
|
|
292
|
+
case 'property_signature': {
|
|
293
|
+
const name = node.childForFieldName('name')
|
|
294
|
+
if (!name) return null
|
|
295
|
+
return {
|
|
296
|
+
name: name.text,
|
|
297
|
+
type: 'variable',
|
|
298
|
+
filePath,
|
|
299
|
+
lineStart: node.startPosition.row + 1,
|
|
300
|
+
lineEnd: node.endPosition.row + 1,
|
|
301
|
+
parentSymbol: parentSymbol ?? undefined,
|
|
302
|
+
exported: isExported,
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
case 'lexical_declaration':
|
|
307
|
+
case 'variable_declaration': {
|
|
308
|
+
return this.extractFromLexicalDeclaration(node, filePath, parentSymbol, isExported)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
default:
|
|
312
|
+
return null
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Extract a symbol from a lexical_declaration (const/let) or variable_declaration (var).
|
|
318
|
+
* Handles arrow functions assigned to variables.
|
|
319
|
+
*/
|
|
320
|
+
private extractFromLexicalDeclaration(
|
|
321
|
+
node: Parser.SyntaxNode,
|
|
322
|
+
filePath: string,
|
|
323
|
+
parentSymbol: string | null,
|
|
324
|
+
isExported: boolean
|
|
325
|
+
): CodeSymbol | null {
|
|
326
|
+
const declarator = node.namedChildren.find(
|
|
327
|
+
(c) => c.type === 'variable_declarator'
|
|
328
|
+
)
|
|
329
|
+
if (!declarator) return null
|
|
330
|
+
|
|
331
|
+
const name = declarator.childForFieldName('name')
|
|
332
|
+
if (!name) return null
|
|
333
|
+
|
|
334
|
+
const value = declarator.childForFieldName('value')
|
|
335
|
+
const isConst = node.text.trimStart().startsWith('const')
|
|
336
|
+
|
|
337
|
+
if (value && (value.type === 'arrow_function' || value.type === 'function_expression' || value.type === 'function')) {
|
|
338
|
+
return {
|
|
339
|
+
name: name.text,
|
|
340
|
+
type: 'function',
|
|
341
|
+
filePath,
|
|
342
|
+
lineStart: node.startPosition.row + 1,
|
|
343
|
+
lineEnd: node.endPosition.row + 1,
|
|
344
|
+
parentSymbol: parentSymbol ?? undefined,
|
|
345
|
+
signature: this.extractSignature(value),
|
|
346
|
+
exported: isExported,
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
name: name.text,
|
|
352
|
+
type: isConst ? 'constant' : 'variable',
|
|
353
|
+
filePath,
|
|
354
|
+
lineStart: node.startPosition.row + 1,
|
|
355
|
+
lineEnd: node.endPosition.row + 1,
|
|
356
|
+
parentSymbol: parentSymbol ?? undefined,
|
|
357
|
+
exported: isExported,
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Extract function signature (parameters + return type).
|
|
363
|
+
*/
|
|
364
|
+
private extractSignature(node: Parser.SyntaxNode): string | undefined {
|
|
365
|
+
const params = node.childForFieldName('parameters')
|
|
366
|
+
const returnType = node.childForFieldName('return_type')
|
|
367
|
+
|
|
368
|
+
if (!params) return undefined
|
|
369
|
+
|
|
370
|
+
let sig = params.text
|
|
371
|
+
if (returnType) {
|
|
372
|
+
// returnType.text may include the leading `:`, so avoid doubling it
|
|
373
|
+
const rt = returnType.text.trimStart()
|
|
374
|
+
sig += rt.startsWith(':') ? ' ' + rt : ': ' + rt
|
|
375
|
+
}
|
|
376
|
+
return sig
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Extract import statements from the AST.
|
|
381
|
+
*/
|
|
382
|
+
private extractImports(tree: Parser.Tree): ImportInfo[] {
|
|
383
|
+
const imports: ImportInfo[] = []
|
|
384
|
+
|
|
385
|
+
for (const child of tree.rootNode.namedChildren) {
|
|
386
|
+
if (child.type === 'import_statement') {
|
|
387
|
+
const info = this.parseImportStatement(child)
|
|
388
|
+
if (info) imports.push(info)
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return imports
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Parse a single import_statement node into an ImportInfo.
|
|
397
|
+
*/
|
|
398
|
+
private parseImportStatement(node: Parser.SyntaxNode): ImportInfo | null {
|
|
399
|
+
const source = node.childForFieldName('source')
|
|
400
|
+
if (!source) return null
|
|
401
|
+
|
|
402
|
+
const sourcePath = source.text.replace(/^['"]|['"]$/g, '')
|
|
403
|
+
const names: string[] = []
|
|
404
|
+
|
|
405
|
+
for (const child of node.namedChildren) {
|
|
406
|
+
if (child.type === 'import_clause') {
|
|
407
|
+
this.collectImportNames(child, names)
|
|
408
|
+
}
|
|
409
|
+
if (child.type === 'namespace_import') {
|
|
410
|
+
const alias = child.childForFieldName('name') ?? child.namedChildren[0]
|
|
411
|
+
if (alias) names.push(alias.text)
|
|
412
|
+
}
|
|
413
|
+
if (child.type === 'named_imports') {
|
|
414
|
+
for (const spec of child.namedChildren) {
|
|
415
|
+
if (spec.type === 'import_specifier') {
|
|
416
|
+
const specName = spec.childForFieldName('name') ?? spec.namedChildren[0]
|
|
417
|
+
if (specName) names.push(specName.text)
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
source: sourcePath,
|
|
425
|
+
names,
|
|
426
|
+
importType: 'import',
|
|
427
|
+
line: node.startPosition.row + 1,
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Collect imported names from an import_clause node.
|
|
433
|
+
*/
|
|
434
|
+
private collectImportNames(node: Parser.SyntaxNode, names: string[]): void {
|
|
435
|
+
for (const child of node.namedChildren) {
|
|
436
|
+
switch (child.type) {
|
|
437
|
+
case 'identifier':
|
|
438
|
+
names.push(child.text)
|
|
439
|
+
break
|
|
440
|
+
case 'named_imports':
|
|
441
|
+
for (const spec of child.namedChildren) {
|
|
442
|
+
if (spec.type === 'import_specifier') {
|
|
443
|
+
const specName = spec.childForFieldName('name') ?? spec.namedChildren[0]
|
|
444
|
+
if (specName) names.push(specName.text)
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
break
|
|
448
|
+
case 'namespace_import': {
|
|
449
|
+
const alias = child.childForFieldName('name') ?? child.namedChildren[0]
|
|
450
|
+
if (alias) names.push(alias.text)
|
|
451
|
+
break
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Check if a node type is a container that may hold declarations.
|
|
459
|
+
*/
|
|
460
|
+
private isContainerNode(type: string): boolean {
|
|
461
|
+
return (
|
|
462
|
+
type === 'program' ||
|
|
463
|
+
type === 'statement_block' ||
|
|
464
|
+
type === 'module' ||
|
|
465
|
+
type === 'export_statement'
|
|
466
|
+
)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Resolve the path to a tree-sitter WASM grammar file.
|
|
471
|
+
*/
|
|
472
|
+
private resolveWasmPath(grammarName: string): string {
|
|
473
|
+
try {
|
|
474
|
+
const wasmsDir = require.resolve('tree-sitter-wasms/package.json')
|
|
475
|
+
return join(wasmsDir, '..', 'out', `tree-sitter-${grammarName}.wasm`)
|
|
476
|
+
} catch {
|
|
477
|
+
return join(
|
|
478
|
+
import.meta.dir,
|
|
479
|
+
'../../node_modules/tree-sitter-wasms/out',
|
|
480
|
+
`tree-sitter-${grammarName}.wasm`
|
|
481
|
+
)
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|