raggiecode 0.2.1__py3-none-any.whl
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.
- Agent/__init__.py +0 -0
- Agent/agent.py +891 -0
- Agent/chat_history_db.py +1500 -0
- Agent/command.py +49 -0
- Agent/config.py +46 -0
- Agent/effort_levels.py +33 -0
- Agent/git_manager.py +727 -0
- Agent/tools.py +35 -0
- Commands/__init__.py +18 -0
- Commands/effort.py +42 -0
- Commands/global_todo.py +23 -0
- Commands/help.py +22 -0
- Commands/reasoning.py +24 -0
- Commands/redo.py +11 -0
- Commands/reindex.py +27 -0
- Commands/shell.py +28 -0
- Commands/stream.py +24 -0
- Commands/undo.py +13 -0
- Commands/unlimited_effort.py +8 -0
- Commands/window_size.py +29 -0
- RAG/__init__.py +0 -0
- RAG/document.py +119 -0
- RAG/find.py +408 -0
- RAG/graph.py +231 -0
- Tools/GetFileCodeStructure.py +43 -0
- Tools/GetSymbolSourceCode.py +27 -0
- Tools/__init__.py +39 -0
- Tools/ask_user.py +102 -0
- Tools/dispatch_subagent.py +215 -0
- Tools/document.py +35 -0
- Tools/edit_symbol.py +250 -0
- Tools/fuzzy_search.py +119 -0
- Tools/list_dir.py +51 -0
- Tools/read.py +49 -0
- Tools/read_image.py +75 -0
- Tools/remove.py +75 -0
- Tools/replace.py +305 -0
- Tools/search.py +41 -0
- Tools/shell.py +149 -0
- Tools/shell_kill.py +87 -0
- Tools/temp_background_service.py +113 -0
- Tools/todo_list.py +481 -0
- Tools/utils.py +116 -0
- Tools/view_changes.py +179 -0
- Tools/walk_call_tree.py +30 -0
- Tools/web_fetch.py +175 -0
- Tools/web_search.py +69 -0
- Tools/write.py +48 -0
- cli.py +111 -0
- config/__init__.py +0 -0
- config/coder_system_prompt.md +119 -0
- config/roles.json +43 -0
- config/tools.json +709 -0
- indexing/__init__.py +0 -0
- indexing/cli.py +128 -0
- indexing/code_index_sdk.py +832 -0
- indexing/code_indexer.py +1763 -0
- indexing/db_schema.py +396 -0
- indexing/export_to_json.py +346 -0
- indexing/extractors.py +189 -0
- indexing/file_utils.py +97 -0
- indexing/frontend/__init__.py +0 -0
- indexing/frontend/css_extractor.py +195 -0
- indexing/frontend/css_parser.py +387 -0
- indexing/frontend/css_selector_utils.py +226 -0
- indexing/frontend/edit_safety.py +573 -0
- indexing/frontend/graph.py +838 -0
- indexing/frontend/html_extractor.py +496 -0
- indexing/frontend/html_parser.py +314 -0
- indexing/frontend/jsx_extractor.py +1204 -0
- indexing/frontend/location_lookup.py +247 -0
- indexing/frontend/resolver.py +485 -0
- indexing/frontend/runtime_resolver.py +862 -0
- indexing/frontend/semantic_output.py +705 -0
- indexing/frontend/source_location.py +69 -0
- indexing/frontend_config.py +72 -0
- indexing/frontend_models.py +347 -0
- indexing/language_config.py +360 -0
- indexing/models.py +284 -0
- indexing/node_utils.py +1112 -0
- indexing/parse_worker.py +1082 -0
- indexing/queries.py +1542 -0
- indexing/sdk_examples.py +426 -0
- interactive.py +248 -0
- raggie.py +673 -0
- raggiecode-0.2.1.dist-info/METADATA +944 -0
- raggiecode-0.2.1.dist-info/RECORD +93 -0
- raggiecode-0.2.1.dist-info/WHEEL +5 -0
- raggiecode-0.2.1.dist-info/entry_points.txt +2 -0
- raggiecode-0.2.1.dist-info/top_level.txt +10 -0
- skills/__init__.py +3 -0
- skills/manager.py +114 -0
- skills/tool.py +121 -0
indexing/queries.py
ADDED
|
@@ -0,0 +1,1542 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Query mixin for CodeIndexSDK.
|
|
4
|
+
All get_* and search_* methods that read entities from the SQLite index.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List, Dict, Optional, Any
|
|
8
|
+
|
|
9
|
+
from indexing.models import (
|
|
10
|
+
Function, Class, Variable, TypeAlias,
|
|
11
|
+
Struct, Interface, Enum, Namespace, File, Dependency,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class QueryMixin:
|
|
16
|
+
"""
|
|
17
|
+
Mixin providing all query methods for the code index.
|
|
18
|
+
Expects self.conn to be a sqlite3.Connection set by the host class.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# ==================== File Queries ====================
|
|
22
|
+
|
|
23
|
+
def get_files(self, language: Optional[str] = None) -> List[File]:
|
|
24
|
+
"""Get all files, optionally filtered by language."""
|
|
25
|
+
cursor = self.conn.cursor()
|
|
26
|
+
if language:
|
|
27
|
+
cursor.execute("SELECT * FROM files WHERE language = ? ORDER BY path", (language,))
|
|
28
|
+
else:
|
|
29
|
+
cursor.execute("SELECT * FROM files ORDER BY path")
|
|
30
|
+
return [File.from_row(row) for row in cursor.fetchall()]
|
|
31
|
+
|
|
32
|
+
def get_file_by_path(self, path: str) -> Optional[File]:
|
|
33
|
+
"""Get a file by its relative path."""
|
|
34
|
+
cursor = self.conn.cursor()
|
|
35
|
+
# Normalize path: strip leading ./ prefix if present
|
|
36
|
+
normalized_path = path[2:] if path.startswith('./') else path
|
|
37
|
+
|
|
38
|
+
cursor.execute("SELECT * FROM files WHERE path = ?", (normalized_path,))
|
|
39
|
+
row = cursor.fetchone()
|
|
40
|
+
return File.from_row(row) if row else None
|
|
41
|
+
|
|
42
|
+
def get_file_by_id(self, file_id: int) -> Optional[File]:
|
|
43
|
+
"""Get a file by its ID."""
|
|
44
|
+
cursor = self.conn.cursor()
|
|
45
|
+
cursor.execute("SELECT * FROM files WHERE id = ?", (file_id,))
|
|
46
|
+
row = cursor.fetchone()
|
|
47
|
+
return File.from_row(row) if row else None
|
|
48
|
+
|
|
49
|
+
def get_files_by_language(self, language: str) -> List[File]:
|
|
50
|
+
"""Get all files for a specific language."""
|
|
51
|
+
return self.get_files(language)
|
|
52
|
+
|
|
53
|
+
# ==================== Function Queries ====================
|
|
54
|
+
|
|
55
|
+
def get_functions(self, file_id: Optional[int] = None) -> List[Function]:
|
|
56
|
+
"""Get all functions, optionally filtered by file."""
|
|
57
|
+
cursor = self.conn.cursor()
|
|
58
|
+
if file_id:
|
|
59
|
+
cursor.execute(
|
|
60
|
+
"SELECT * FROM functions WHERE file_id = ? AND parent_id IS NULL ORDER BY name",
|
|
61
|
+
(file_id,)
|
|
62
|
+
)
|
|
63
|
+
else:
|
|
64
|
+
cursor.execute(
|
|
65
|
+
"SELECT * FROM functions WHERE parent_id IS NULL ORDER BY name"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
functions = []
|
|
69
|
+
for row in cursor.fetchall():
|
|
70
|
+
file = self.get_file_by_id(row['file_id'])
|
|
71
|
+
if file:
|
|
72
|
+
functions.append(Function.from_row(row, file.path))
|
|
73
|
+
return functions
|
|
74
|
+
|
|
75
|
+
def get_function_by_id(self, func_id: int) -> Optional[Function]:
|
|
76
|
+
"""Get a function by its ID."""
|
|
77
|
+
cursor = self.conn.cursor()
|
|
78
|
+
cursor.execute("SELECT * FROM functions WHERE id = ?", (func_id,))
|
|
79
|
+
row = cursor.fetchone()
|
|
80
|
+
if row:
|
|
81
|
+
file = self.get_file_by_id(row['file_id'])
|
|
82
|
+
return Function.from_row(row, file.path) if file else None
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
def get_function_by_name(self, name: str, file_id: Optional[int] = None) -> List[Function]:
|
|
86
|
+
"""Get functions by name, optionally filtered by file."""
|
|
87
|
+
cursor = self.conn.cursor()
|
|
88
|
+
if file_id:
|
|
89
|
+
cursor.execute(
|
|
90
|
+
"SELECT * FROM functions WHERE name = ? AND file_id = ? ORDER BY id",
|
|
91
|
+
(name, file_id)
|
|
92
|
+
)
|
|
93
|
+
else:
|
|
94
|
+
cursor.execute(
|
|
95
|
+
"SELECT * FROM functions WHERE name = ? ORDER BY id",
|
|
96
|
+
(name,)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
functions = []
|
|
100
|
+
for row in cursor.fetchall():
|
|
101
|
+
file = self.get_file_by_id(row['file_id'])
|
|
102
|
+
if file:
|
|
103
|
+
functions.append(Function.from_row(row, file.path))
|
|
104
|
+
return functions
|
|
105
|
+
|
|
106
|
+
def search_functions(self, pattern: str, file_id: Optional[int] = None) -> List[Function]:
|
|
107
|
+
"""Search functions by name pattern (SQL LIKE)."""
|
|
108
|
+
cursor = self.conn.cursor()
|
|
109
|
+
if file_id:
|
|
110
|
+
cursor.execute(
|
|
111
|
+
"SELECT * FROM functions WHERE name LIKE ? AND file_id = ? ORDER BY name",
|
|
112
|
+
(f"%{pattern}%", file_id)
|
|
113
|
+
)
|
|
114
|
+
else:
|
|
115
|
+
cursor.execute(
|
|
116
|
+
"SELECT * FROM functions WHERE name LIKE ? ORDER BY name",
|
|
117
|
+
(f"%{pattern}%",)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
functions = []
|
|
121
|
+
for row in cursor.fetchall():
|
|
122
|
+
file = self.get_file_by_id(row['file_id'])
|
|
123
|
+
if file:
|
|
124
|
+
functions.append(Function.from_row(row, file.path))
|
|
125
|
+
return functions
|
|
126
|
+
|
|
127
|
+
def get_file_functions(self, file_id: int) -> List[Function]:
|
|
128
|
+
"""Get all top-level functions in a file."""
|
|
129
|
+
return self.get_functions(file_id)
|
|
130
|
+
|
|
131
|
+
def get_functions_by_complexity(self,
|
|
132
|
+
min_branches: Optional[int] = None,
|
|
133
|
+
min_lines: Optional[int] = None,
|
|
134
|
+
max_branches: Optional[int] = None,
|
|
135
|
+
max_lines: Optional[int] = None,
|
|
136
|
+
match_any: bool = False,
|
|
137
|
+
file_id: Optional[int] = None) -> List[Function]:
|
|
138
|
+
"""Filter functions by branch count and/or lines of code.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
min_branches: Minimum number of branches (inclusive)
|
|
142
|
+
min_lines: Minimum lines of code (inclusive)
|
|
143
|
+
max_branches: Maximum number of branches (inclusive)
|
|
144
|
+
max_lines: Maximum lines of code (inclusive)
|
|
145
|
+
match_any: If True, uses LOGICAL OR (match either condition).
|
|
146
|
+
If False, uses LOGICAL AND (match all conditions).
|
|
147
|
+
file_id: Optional file ID to filter by file
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
List of Function objects matching the criteria
|
|
151
|
+
"""
|
|
152
|
+
cursor = self.conn.cursor()
|
|
153
|
+
conditions = []
|
|
154
|
+
params = []
|
|
155
|
+
|
|
156
|
+
# Build branch count conditions
|
|
157
|
+
if min_branches is not None:
|
|
158
|
+
conditions.append("branch_count >= ?")
|
|
159
|
+
params.append(min_branches)
|
|
160
|
+
if max_branches is not None:
|
|
161
|
+
conditions.append("branch_count <= ?")
|
|
162
|
+
params.append(max_branches)
|
|
163
|
+
|
|
164
|
+
# Build lines of code conditions (parsed from location JSON)
|
|
165
|
+
# location format: {"start_line": X, "end_line": Y, ...}
|
|
166
|
+
if min_lines is not None:
|
|
167
|
+
conditions.append("(json_extract(location, '$.end_line') - json_extract(location, '$.start_line') + 1) >= ?")
|
|
168
|
+
params.append(min_lines)
|
|
169
|
+
if max_lines is not None:
|
|
170
|
+
conditions.append("(json_extract(location, '$.end_line') - json_extract(location, '$.start_line') + 1) <= ?")
|
|
171
|
+
params.append(max_lines)
|
|
172
|
+
|
|
173
|
+
if not conditions:
|
|
174
|
+
return self.get_functions(file_id)
|
|
175
|
+
|
|
176
|
+
# Determine operator: AND for all conditions, OR if match_any is True
|
|
177
|
+
operator = " OR " if match_any else " AND "
|
|
178
|
+
where_clause = f"parent_id IS NULL AND {operator.join(conditions)}"
|
|
179
|
+
|
|
180
|
+
if file_id is not None:
|
|
181
|
+
where_clause = f"file_id = ? AND {where_clause}"
|
|
182
|
+
params.insert(0, file_id)
|
|
183
|
+
|
|
184
|
+
query = f"SELECT * FROM functions WHERE {where_clause} ORDER BY branch_count DESC, name"
|
|
185
|
+
cursor.execute(query, params)
|
|
186
|
+
|
|
187
|
+
functions = []
|
|
188
|
+
for row in cursor.fetchall():
|
|
189
|
+
file = self.get_file_by_id(row['file_id'])
|
|
190
|
+
if file:
|
|
191
|
+
functions.append(Function.from_row(row, file.path))
|
|
192
|
+
return functions
|
|
193
|
+
|
|
194
|
+
def get_complex_symbols(self,
|
|
195
|
+
min_branches: int = 5,
|
|
196
|
+
min_lines: int = 30,
|
|
197
|
+
match_any: bool = True) -> List[Function]:
|
|
198
|
+
"""Get "complex" functions matching either branch or LOC thresholds.
|
|
199
|
+
|
|
200
|
+
Convenience method that finds functions that are likely complex:
|
|
201
|
+
- High branching (many conditionals)
|
|
202
|
+
- Long functions (many lines)
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
min_branches: Minimum branches threshold (default: 5)
|
|
206
|
+
min_lines: Minimum lines threshold (default: 30)
|
|
207
|
+
match_any: If True, returns functions with EITHER high branches OR long LOC.
|
|
208
|
+
If False, returns functions with BOTH high branches AND long LOC.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
List of Function objects sorted by complexity (branches desc)
|
|
212
|
+
"""
|
|
213
|
+
return self.get_functions_by_complexity(
|
|
214
|
+
min_branches=min_branches,
|
|
215
|
+
min_lines=min_lines,
|
|
216
|
+
match_any=match_any
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
def get_methods_by_complexity(self,
|
|
220
|
+
class_id: int,
|
|
221
|
+
min_branches: Optional[int] = None,
|
|
222
|
+
min_lines: Optional[int] = None,
|
|
223
|
+
max_branches: Optional[int] = None,
|
|
224
|
+
max_lines: Optional[int] = None,
|
|
225
|
+
match_any: bool = False) -> List[Function]:
|
|
226
|
+
"""Filter methods of a class by branch count and/or lines of code.
|
|
227
|
+
|
|
228
|
+
Args match get_functions_by_complexity().
|
|
229
|
+
"""
|
|
230
|
+
cursor = self.conn.cursor()
|
|
231
|
+
conditions = ["parent_id = ?", "parent_type = 'class'"]
|
|
232
|
+
params = [class_id]
|
|
233
|
+
|
|
234
|
+
if min_branches is not None:
|
|
235
|
+
conditions.append("branch_count >= ?")
|
|
236
|
+
params.append(min_branches)
|
|
237
|
+
if max_branches is not None:
|
|
238
|
+
conditions.append("branch_count <= ?")
|
|
239
|
+
params.append(max_branches)
|
|
240
|
+
if min_lines is not None:
|
|
241
|
+
conditions.append("(json_extract(location, '$.end_line') - json_extract(location, '$.start_line') + 1) >= ?")
|
|
242
|
+
params.append(min_lines)
|
|
243
|
+
if max_lines is not None:
|
|
244
|
+
conditions.append("(json_extract(location, '$.end_line') - json_extract(location, '$.start_line') + 1) <= ?")
|
|
245
|
+
params.append(max_lines)
|
|
246
|
+
|
|
247
|
+
operator = " OR " if match_any else " AND "
|
|
248
|
+
where_clause = operator.join(conditions)
|
|
249
|
+
query = f"SELECT * FROM functions WHERE {where_clause} ORDER BY branch_count DESC, name"
|
|
250
|
+
|
|
251
|
+
cursor.execute(query, params)
|
|
252
|
+
methods = []
|
|
253
|
+
for row in cursor.fetchall():
|
|
254
|
+
file = self.get_file_by_id(row['file_id'])
|
|
255
|
+
if file:
|
|
256
|
+
methods.append(Function.from_row(row, file.path))
|
|
257
|
+
return methods
|
|
258
|
+
|
|
259
|
+
# ==================== Method Queries ====================
|
|
260
|
+
|
|
261
|
+
def get_methods(self, class_id: int) -> List[Function]:
|
|
262
|
+
"""Get all methods of a class."""
|
|
263
|
+
cursor = self.conn.cursor()
|
|
264
|
+
cursor.execute(
|
|
265
|
+
"SELECT * FROM functions WHERE parent_id = ? AND parent_type = 'class' ORDER BY name",
|
|
266
|
+
(class_id,)
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
methods = []
|
|
270
|
+
for row in cursor.fetchall():
|
|
271
|
+
file = self.get_file_by_id(row['file_id'])
|
|
272
|
+
if file:
|
|
273
|
+
methods.append(Function.from_row(row, file.path))
|
|
274
|
+
return methods
|
|
275
|
+
|
|
276
|
+
def get_method_by_name(self, class_id: int, name: str) -> Optional[Function]:
|
|
277
|
+
"""Get a method by name within a class."""
|
|
278
|
+
cursor = self.conn.cursor()
|
|
279
|
+
cursor.execute(
|
|
280
|
+
"SELECT * FROM functions WHERE parent_id = ? AND parent_type = 'class' AND name = ?",
|
|
281
|
+
(class_id, name)
|
|
282
|
+
)
|
|
283
|
+
row = cursor.fetchone()
|
|
284
|
+
if row:
|
|
285
|
+
file = self.get_file_by_id(row['file_id'])
|
|
286
|
+
return Function.from_row(row, file.path) if file else None
|
|
287
|
+
return None
|
|
288
|
+
|
|
289
|
+
# ==================== Class Queries ====================
|
|
290
|
+
|
|
291
|
+
def get_classes(self, file_id: Optional[int] = None) -> List[Class]:
|
|
292
|
+
"""Get all top-level classes, optionally filtered by file."""
|
|
293
|
+
cursor = self.conn.cursor()
|
|
294
|
+
if file_id:
|
|
295
|
+
cursor.execute(
|
|
296
|
+
"SELECT * FROM classes WHERE file_id = ? AND parent_id IS NULL ORDER BY name",
|
|
297
|
+
(file_id,)
|
|
298
|
+
)
|
|
299
|
+
else:
|
|
300
|
+
cursor.execute(
|
|
301
|
+
"SELECT * FROM classes WHERE parent_id IS NULL ORDER BY name"
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
classes = []
|
|
305
|
+
for row in cursor.fetchall():
|
|
306
|
+
file = self.get_file_by_id(row['file_id'])
|
|
307
|
+
if file:
|
|
308
|
+
classes.append(Class.from_row(row, file.path))
|
|
309
|
+
return classes
|
|
310
|
+
|
|
311
|
+
def get_class_by_id(self, class_id: int) -> Optional[Class]:
|
|
312
|
+
"""Get a class by its ID."""
|
|
313
|
+
cursor = self.conn.cursor()
|
|
314
|
+
cursor.execute("SELECT * FROM classes WHERE id = ?", (class_id,))
|
|
315
|
+
row = cursor.fetchone()
|
|
316
|
+
if row:
|
|
317
|
+
file = self.get_file_by_id(row['file_id'])
|
|
318
|
+
return Class.from_row(row, file.path) if file else None
|
|
319
|
+
return None
|
|
320
|
+
|
|
321
|
+
def get_class_by_name(self, name: str, file_id: Optional[int] = None) -> List[Class]:
|
|
322
|
+
"""Get classes by name, optionally filtered by file."""
|
|
323
|
+
cursor = self.conn.cursor()
|
|
324
|
+
if file_id:
|
|
325
|
+
cursor.execute(
|
|
326
|
+
"SELECT * FROM classes WHERE name = ? AND file_id = ? ORDER BY id",
|
|
327
|
+
(name, file_id)
|
|
328
|
+
)
|
|
329
|
+
else:
|
|
330
|
+
cursor.execute(
|
|
331
|
+
"SELECT * FROM classes WHERE name = ? ORDER BY id",
|
|
332
|
+
(name,)
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
classes = []
|
|
336
|
+
for row in cursor.fetchall():
|
|
337
|
+
file = self.get_file_by_id(row['file_id'])
|
|
338
|
+
if file:
|
|
339
|
+
classes.append(Class.from_row(row, file.path))
|
|
340
|
+
return classes
|
|
341
|
+
|
|
342
|
+
def search_classes(self, pattern: str, file_id: Optional[int] = None) -> List[Class]:
|
|
343
|
+
"""Search classes by name pattern (SQL LIKE)."""
|
|
344
|
+
cursor = self.conn.cursor()
|
|
345
|
+
if file_id:
|
|
346
|
+
cursor.execute(
|
|
347
|
+
"SELECT * FROM classes WHERE name LIKE ? AND file_id = ? ORDER BY name",
|
|
348
|
+
(f"%{pattern}%", file_id)
|
|
349
|
+
)
|
|
350
|
+
else:
|
|
351
|
+
cursor.execute(
|
|
352
|
+
"SELECT * FROM classes WHERE name LIKE ? ORDER BY name",
|
|
353
|
+
(f"%{pattern}%",)
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
classes = []
|
|
357
|
+
for row in cursor.fetchall():
|
|
358
|
+
file = self.get_file_by_id(row['file_id'])
|
|
359
|
+
if file:
|
|
360
|
+
classes.append(Class.from_row(row, file.path))
|
|
361
|
+
return classes
|
|
362
|
+
|
|
363
|
+
def get_file_classes(self, file_id: int) -> List[Class]:
|
|
364
|
+
"""Get all top-level classes in a file."""
|
|
365
|
+
return self.get_classes(file_id)
|
|
366
|
+
|
|
367
|
+
def get_nested_classes(self, class_id: int) -> List[Class]:
|
|
368
|
+
"""Get all nested classes within a class."""
|
|
369
|
+
cursor = self.conn.cursor()
|
|
370
|
+
cursor.execute(
|
|
371
|
+
"SELECT * FROM classes WHERE parent_id = ? ORDER BY name",
|
|
372
|
+
(class_id,)
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
classes = []
|
|
376
|
+
for row in cursor.fetchall():
|
|
377
|
+
file = self.get_file_by_id(row['file_id'])
|
|
378
|
+
if file:
|
|
379
|
+
classes.append(Class.from_row(row, file.path))
|
|
380
|
+
return classes
|
|
381
|
+
|
|
382
|
+
def get_class_methods(self, class_id: int) -> List[Function]:
|
|
383
|
+
"""Get all methods of a class."""
|
|
384
|
+
return self.get_methods(class_id)
|
|
385
|
+
|
|
386
|
+
def get_class_variables(self, class_id: int) -> List[Variable]:
|
|
387
|
+
"""Get all class attributes/variables."""
|
|
388
|
+
cursor = self.conn.cursor()
|
|
389
|
+
cursor.execute(
|
|
390
|
+
"SELECT * FROM variables WHERE parent_id = ? AND parent_type = 'class' ORDER BY name",
|
|
391
|
+
(class_id,)
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
variables = []
|
|
395
|
+
for row in cursor.fetchall():
|
|
396
|
+
file = self.get_file_by_id(row['file_id'])
|
|
397
|
+
if file:
|
|
398
|
+
variables.append(Variable.from_row(row, file.path))
|
|
399
|
+
return variables
|
|
400
|
+
|
|
401
|
+
# ==================== Variable Queries ====================
|
|
402
|
+
|
|
403
|
+
def get_variables(self, file_id: Optional[int] = None) -> List[Variable]:
|
|
404
|
+
"""Get all top-level variables, optionally filtered by file."""
|
|
405
|
+
cursor = self.conn.cursor()
|
|
406
|
+
if file_id:
|
|
407
|
+
cursor.execute(
|
|
408
|
+
"SELECT * FROM variables WHERE file_id = ? AND parent_id IS NULL ORDER BY name",
|
|
409
|
+
(file_id,)
|
|
410
|
+
)
|
|
411
|
+
else:
|
|
412
|
+
cursor.execute(
|
|
413
|
+
"SELECT * FROM variables WHERE parent_id IS NULL ORDER BY name"
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
variables = []
|
|
417
|
+
for row in cursor.fetchall():
|
|
418
|
+
file = self.get_file_by_id(row['file_id'])
|
|
419
|
+
if file:
|
|
420
|
+
variables.append(Variable.from_row(row, file.path))
|
|
421
|
+
return variables
|
|
422
|
+
|
|
423
|
+
def get_variable_by_id(self, var_id: int) -> Optional[Variable]:
|
|
424
|
+
"""Get a variable by its ID."""
|
|
425
|
+
cursor = self.conn.cursor()
|
|
426
|
+
cursor.execute("SELECT * FROM variables WHERE id = ?", (var_id,))
|
|
427
|
+
row = cursor.fetchone()
|
|
428
|
+
if row:
|
|
429
|
+
file = self.get_file_by_id(row['file_id'])
|
|
430
|
+
return Variable.from_row(row, file.path) if file else None
|
|
431
|
+
return None
|
|
432
|
+
|
|
433
|
+
def get_variable_by_name(self, name: str, file_id: Optional[int] = None) -> List[Variable]:
|
|
434
|
+
"""Get variables by name, optionally filtered by file."""
|
|
435
|
+
cursor = self.conn.cursor()
|
|
436
|
+
if file_id:
|
|
437
|
+
cursor.execute(
|
|
438
|
+
"SELECT * FROM variables WHERE name = ? AND file_id = ? ORDER BY id",
|
|
439
|
+
(name, file_id)
|
|
440
|
+
)
|
|
441
|
+
else:
|
|
442
|
+
cursor.execute(
|
|
443
|
+
"SELECT * FROM variables WHERE name = ? ORDER BY id",
|
|
444
|
+
(name,)
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
variables = []
|
|
448
|
+
for row in cursor.fetchall():
|
|
449
|
+
file = self.get_file_by_id(row['file_id'])
|
|
450
|
+
if file:
|
|
451
|
+
variables.append(Variable.from_row(row, file.path))
|
|
452
|
+
return variables
|
|
453
|
+
|
|
454
|
+
def search_variables(self, pattern: str, file_id: Optional[int] = None) -> List[Variable]:
|
|
455
|
+
"""Search variables by name pattern (SQL LIKE)."""
|
|
456
|
+
cursor = self.conn.cursor()
|
|
457
|
+
if file_id:
|
|
458
|
+
cursor.execute(
|
|
459
|
+
"SELECT * FROM variables WHERE name LIKE ? AND file_id = ? ORDER BY name",
|
|
460
|
+
(f"%{pattern}%", file_id)
|
|
461
|
+
)
|
|
462
|
+
else:
|
|
463
|
+
cursor.execute(
|
|
464
|
+
"SELECT * FROM variables WHERE name LIKE ? ORDER BY name",
|
|
465
|
+
(f"%{pattern}%",)
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
variables = []
|
|
469
|
+
for row in cursor.fetchall():
|
|
470
|
+
file = self.get_file_by_id(row['file_id'])
|
|
471
|
+
if file:
|
|
472
|
+
variables.append(Variable.from_row(row, file.path))
|
|
473
|
+
return variables
|
|
474
|
+
|
|
475
|
+
def get_file_variables(self, file_id: int) -> List[Variable]:
|
|
476
|
+
"""Get all top-level variables in a file."""
|
|
477
|
+
return self.get_variables(file_id)
|
|
478
|
+
|
|
479
|
+
# ==================== Type Alias Queries ====================
|
|
480
|
+
|
|
481
|
+
def get_type_aliases(self, file_id: Optional[int] = None) -> List[TypeAlias]:
|
|
482
|
+
"""Get all type aliases, optionally filtered by file."""
|
|
483
|
+
cursor = self.conn.cursor()
|
|
484
|
+
if file_id:
|
|
485
|
+
cursor.execute(
|
|
486
|
+
"SELECT * FROM type_aliases WHERE file_id = ? ORDER BY name",
|
|
487
|
+
(file_id,)
|
|
488
|
+
)
|
|
489
|
+
else:
|
|
490
|
+
cursor.execute("SELECT * FROM type_aliases ORDER BY name")
|
|
491
|
+
|
|
492
|
+
type_aliases = []
|
|
493
|
+
for row in cursor.fetchall():
|
|
494
|
+
file = self.get_file_by_id(row['file_id'])
|
|
495
|
+
if file:
|
|
496
|
+
type_aliases.append(TypeAlias.from_row(row, file.path))
|
|
497
|
+
return type_aliases
|
|
498
|
+
|
|
499
|
+
def get_type_alias_by_id(self, alias_id: int) -> Optional[TypeAlias]:
|
|
500
|
+
"""Get a type alias by its ID."""
|
|
501
|
+
cursor = self.conn.cursor()
|
|
502
|
+
cursor.execute("SELECT * FROM type_aliases WHERE id = ?", (alias_id,))
|
|
503
|
+
row = cursor.fetchone()
|
|
504
|
+
if row:
|
|
505
|
+
file = self.get_file_by_id(row['file_id'])
|
|
506
|
+
return TypeAlias.from_row(row, file.path) if file else None
|
|
507
|
+
return None
|
|
508
|
+
|
|
509
|
+
def get_type_alias_by_name(self, name: str, file_id: Optional[int] = None) -> List[TypeAlias]:
|
|
510
|
+
"""Get type aliases by name, optionally filtered by file."""
|
|
511
|
+
cursor = self.conn.cursor()
|
|
512
|
+
if file_id:
|
|
513
|
+
cursor.execute(
|
|
514
|
+
"SELECT * FROM type_aliases WHERE name = ? AND file_id = ? ORDER BY id",
|
|
515
|
+
(name, file_id)
|
|
516
|
+
)
|
|
517
|
+
else:
|
|
518
|
+
cursor.execute(
|
|
519
|
+
"SELECT * FROM type_aliases WHERE name = ? ORDER BY id",
|
|
520
|
+
(name,)
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
type_aliases = []
|
|
524
|
+
for row in cursor.fetchall():
|
|
525
|
+
file = self.get_file_by_id(row['file_id'])
|
|
526
|
+
if file:
|
|
527
|
+
type_aliases.append(TypeAlias.from_row(row, file.path))
|
|
528
|
+
return type_aliases
|
|
529
|
+
|
|
530
|
+
def search_type_aliases(self, pattern: str, file_id: Optional[int] = None) -> List[TypeAlias]:
|
|
531
|
+
"""Search type aliases by name pattern (SQL LIKE)."""
|
|
532
|
+
cursor = self.conn.cursor()
|
|
533
|
+
if file_id:
|
|
534
|
+
cursor.execute(
|
|
535
|
+
"SELECT * FROM type_aliases WHERE name LIKE ? AND file_id = ? ORDER BY name",
|
|
536
|
+
(f"%{pattern}%", file_id)
|
|
537
|
+
)
|
|
538
|
+
else:
|
|
539
|
+
cursor.execute(
|
|
540
|
+
"SELECT * FROM type_aliases WHERE name LIKE ? ORDER BY name",
|
|
541
|
+
(f"%{pattern}%",)
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
type_aliases = []
|
|
545
|
+
for row in cursor.fetchall():
|
|
546
|
+
file = self.get_file_by_id(row['file_id'])
|
|
547
|
+
if file:
|
|
548
|
+
type_aliases.append(TypeAlias.from_row(row, file.path))
|
|
549
|
+
return type_aliases
|
|
550
|
+
|
|
551
|
+
# ==================== Struct Queries ====================
|
|
552
|
+
|
|
553
|
+
def get_structs(self, file_id: Optional[int] = None) -> List[Struct]:
|
|
554
|
+
"""Get all structs, optionally filtered by file."""
|
|
555
|
+
cursor = self.conn.cursor()
|
|
556
|
+
if file_id:
|
|
557
|
+
cursor.execute(
|
|
558
|
+
"SELECT * FROM structs WHERE file_id = ? ORDER BY name",
|
|
559
|
+
(file_id,)
|
|
560
|
+
)
|
|
561
|
+
else:
|
|
562
|
+
cursor.execute("SELECT * FROM structs ORDER BY name")
|
|
563
|
+
|
|
564
|
+
structs = []
|
|
565
|
+
for row in cursor.fetchall():
|
|
566
|
+
file = self.get_file_by_id(row['file_id'])
|
|
567
|
+
if file:
|
|
568
|
+
structs.append(Struct.from_row(row, file.path))
|
|
569
|
+
return structs
|
|
570
|
+
|
|
571
|
+
def get_struct_by_id(self, struct_id: int) -> Optional[Struct]:
|
|
572
|
+
"""Get a struct by its ID."""
|
|
573
|
+
cursor = self.conn.cursor()
|
|
574
|
+
cursor.execute("SELECT * FROM structs WHERE id = ?", (struct_id,))
|
|
575
|
+
row = cursor.fetchone()
|
|
576
|
+
if row:
|
|
577
|
+
file = self.get_file_by_id(row['file_id'])
|
|
578
|
+
return Struct.from_row(row, file.path) if file else None
|
|
579
|
+
return None
|
|
580
|
+
|
|
581
|
+
def get_struct_by_name(self, name: str, file_id: Optional[int] = None) -> List[Struct]:
|
|
582
|
+
"""Get structs by name, optionally filtered by file."""
|
|
583
|
+
cursor = self.conn.cursor()
|
|
584
|
+
if file_id:
|
|
585
|
+
cursor.execute(
|
|
586
|
+
"SELECT * FROM structs WHERE name = ? AND file_id = ? ORDER BY id",
|
|
587
|
+
(name, file_id)
|
|
588
|
+
)
|
|
589
|
+
else:
|
|
590
|
+
cursor.execute(
|
|
591
|
+
"SELECT * FROM structs WHERE name = ? ORDER BY id",
|
|
592
|
+
(name,)
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
structs = []
|
|
596
|
+
for row in cursor.fetchall():
|
|
597
|
+
file = self.get_file_by_id(row['file_id'])
|
|
598
|
+
if file:
|
|
599
|
+
structs.append(Struct.from_row(row, file.path))
|
|
600
|
+
return structs
|
|
601
|
+
|
|
602
|
+
# ==================== Enum Queries ====================
|
|
603
|
+
|
|
604
|
+
def get_enums(self, file_id: Optional[int] = None) -> List[Enum]:
|
|
605
|
+
"""Get all enums, optionally filtered by file."""
|
|
606
|
+
cursor = self.conn.cursor()
|
|
607
|
+
if file_id:
|
|
608
|
+
cursor.execute(
|
|
609
|
+
"SELECT * FROM enums WHERE file_id = ? ORDER BY name",
|
|
610
|
+
(file_id,)
|
|
611
|
+
)
|
|
612
|
+
else:
|
|
613
|
+
cursor.execute("SELECT * FROM enums ORDER BY name")
|
|
614
|
+
|
|
615
|
+
enums = []
|
|
616
|
+
for row in cursor.fetchall():
|
|
617
|
+
file = self.get_file_by_id(row['file_id'])
|
|
618
|
+
if file:
|
|
619
|
+
enums.append(Enum.from_row(row, file.path))
|
|
620
|
+
return enums
|
|
621
|
+
|
|
622
|
+
def get_enum_by_id(self, enum_id: int) -> Optional[Enum]:
|
|
623
|
+
"""Get an enum by its ID."""
|
|
624
|
+
cursor = self.conn.cursor()
|
|
625
|
+
cursor.execute("SELECT * FROM enums WHERE id = ?", (enum_id,))
|
|
626
|
+
row = cursor.fetchone()
|
|
627
|
+
if row:
|
|
628
|
+
file = self.get_file_by_id(row['file_id'])
|
|
629
|
+
return Enum.from_row(row, file.path) if file else None
|
|
630
|
+
return None
|
|
631
|
+
|
|
632
|
+
def get_enum_by_name(self, name: str, file_id: Optional[int] = None) -> List[Enum]:
|
|
633
|
+
"""Get enums by name, optionally filtered by file."""
|
|
634
|
+
cursor = self.conn.cursor()
|
|
635
|
+
if file_id:
|
|
636
|
+
cursor.execute(
|
|
637
|
+
"SELECT * FROM enums WHERE name = ? AND file_id = ? ORDER BY id",
|
|
638
|
+
(name, file_id)
|
|
639
|
+
)
|
|
640
|
+
else:
|
|
641
|
+
cursor.execute(
|
|
642
|
+
"SELECT * FROM enums WHERE name = ? ORDER BY id",
|
|
643
|
+
(name,)
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
enums = []
|
|
647
|
+
for row in cursor.fetchall():
|
|
648
|
+
file = self.get_file_by_id(row['file_id'])
|
|
649
|
+
if file:
|
|
650
|
+
enums.append(Enum.from_row(row, file.path))
|
|
651
|
+
return enums
|
|
652
|
+
|
|
653
|
+
# ==================== Namespace Queries ====================
|
|
654
|
+
|
|
655
|
+
def get_namespaces(self, file_id: Optional[int] = None) -> List[Namespace]:
|
|
656
|
+
"""Get all namespaces, optionally filtered by file."""
|
|
657
|
+
cursor = self.conn.cursor()
|
|
658
|
+
if file_id:
|
|
659
|
+
cursor.execute(
|
|
660
|
+
"SELECT * FROM namespaces WHERE file_id = ? ORDER BY name",
|
|
661
|
+
(file_id,)
|
|
662
|
+
)
|
|
663
|
+
else:
|
|
664
|
+
cursor.execute("SELECT * FROM namespaces ORDER BY name")
|
|
665
|
+
|
|
666
|
+
namespaces = []
|
|
667
|
+
for row in cursor.fetchall():
|
|
668
|
+
file = self.get_file_by_id(row['file_id'])
|
|
669
|
+
if file:
|
|
670
|
+
namespaces.append(Namespace.from_row(row, file.path))
|
|
671
|
+
return namespaces
|
|
672
|
+
|
|
673
|
+
def get_namespace_by_id(self, namespace_id: int) -> Optional[Namespace]:
|
|
674
|
+
"""Get a namespace by its ID."""
|
|
675
|
+
cursor = self.conn.cursor()
|
|
676
|
+
cursor.execute("SELECT * FROM namespaces WHERE id = ?", (namespace_id,))
|
|
677
|
+
row = cursor.fetchone()
|
|
678
|
+
if row:
|
|
679
|
+
file = self.get_file_by_id(row['file_id'])
|
|
680
|
+
return Namespace.from_row(row, file.path) if file else None
|
|
681
|
+
return None
|
|
682
|
+
|
|
683
|
+
def get_namespace_by_name(self, name: str, file_id: Optional[int] = None) -> List[Namespace]:
|
|
684
|
+
"""Get namespaces by name, optionally filtered by file."""
|
|
685
|
+
cursor = self.conn.cursor()
|
|
686
|
+
if file_id:
|
|
687
|
+
cursor.execute(
|
|
688
|
+
"SELECT * FROM namespaces WHERE name = ? AND file_id = ? ORDER BY id",
|
|
689
|
+
(name, file_id)
|
|
690
|
+
)
|
|
691
|
+
else:
|
|
692
|
+
cursor.execute(
|
|
693
|
+
"SELECT * FROM namespaces WHERE name = ? ORDER BY id",
|
|
694
|
+
(name,)
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
namespaces = []
|
|
698
|
+
for row in cursor.fetchall():
|
|
699
|
+
file = self.get_file_by_id(row['file_id'])
|
|
700
|
+
if file:
|
|
701
|
+
namespaces.append(Namespace.from_row(row, file.path))
|
|
702
|
+
return namespaces
|
|
703
|
+
|
|
704
|
+
def get_file_namespaces(self, file_id: int) -> List[Namespace]:
|
|
705
|
+
"""Get all namespaces in a file."""
|
|
706
|
+
cursor = self.conn.cursor()
|
|
707
|
+
cursor.execute(
|
|
708
|
+
"SELECT * FROM namespaces WHERE file_id = ? ORDER BY name",
|
|
709
|
+
(file_id,)
|
|
710
|
+
)
|
|
711
|
+
namespaces = []
|
|
712
|
+
for row in cursor.fetchall():
|
|
713
|
+
file = self.get_file_by_id(row['file_id'])
|
|
714
|
+
if file:
|
|
715
|
+
namespaces.append(Namespace.from_row(row, file.path))
|
|
716
|
+
return namespaces
|
|
717
|
+
|
|
718
|
+
# ==================== Interface Queries ====================
|
|
719
|
+
|
|
720
|
+
def get_interfaces(self, file_id: Optional[int] = None) -> List[Interface]:
|
|
721
|
+
"""Get all interfaces, optionally filtered by file."""
|
|
722
|
+
cursor = self.conn.cursor()
|
|
723
|
+
if file_id:
|
|
724
|
+
cursor.execute(
|
|
725
|
+
"SELECT * FROM interfaces WHERE file_id = ? ORDER BY name",
|
|
726
|
+
(file_id,)
|
|
727
|
+
)
|
|
728
|
+
else:
|
|
729
|
+
cursor.execute("SELECT * FROM interfaces ORDER BY name")
|
|
730
|
+
|
|
731
|
+
interfaces = []
|
|
732
|
+
for row in cursor.fetchall():
|
|
733
|
+
file = self.get_file_by_id(row['file_id'])
|
|
734
|
+
if file:
|
|
735
|
+
interfaces.append(Interface.from_row(row, file.path))
|
|
736
|
+
return interfaces
|
|
737
|
+
|
|
738
|
+
def get_interface_by_id(self, interface_id: int) -> Optional[Interface]:
|
|
739
|
+
"""Get an interface by its ID."""
|
|
740
|
+
cursor = self.conn.cursor()
|
|
741
|
+
cursor.execute("SELECT * FROM interfaces WHERE id = ?", (interface_id,))
|
|
742
|
+
row = cursor.fetchone()
|
|
743
|
+
if row:
|
|
744
|
+
file = self.get_file_by_id(row['file_id'])
|
|
745
|
+
return Interface.from_row(row, file.path) if file else None
|
|
746
|
+
return None
|
|
747
|
+
|
|
748
|
+
def get_interface_by_name(self, name: str, file_id: Optional[int] = None) -> List[Interface]:
|
|
749
|
+
"""Get interfaces by name, optionally filtered by file."""
|
|
750
|
+
cursor = self.conn.cursor()
|
|
751
|
+
if file_id:
|
|
752
|
+
cursor.execute(
|
|
753
|
+
"SELECT * FROM interfaces WHERE name = ? AND file_id = ? ORDER BY id",
|
|
754
|
+
(name, file_id)
|
|
755
|
+
)
|
|
756
|
+
else:
|
|
757
|
+
cursor.execute(
|
|
758
|
+
"SELECT * FROM interfaces WHERE name = ? ORDER BY id",
|
|
759
|
+
(name,)
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
interfaces = []
|
|
763
|
+
for row in cursor.fetchall():
|
|
764
|
+
file = self.get_file_by_id(row['file_id'])
|
|
765
|
+
if file:
|
|
766
|
+
interfaces.append(Interface.from_row(row, file.path))
|
|
767
|
+
return interfaces
|
|
768
|
+
|
|
769
|
+
# ==================== Statistics ====================
|
|
770
|
+
|
|
771
|
+
def get_statistics(self) -> Dict[str, int]:
|
|
772
|
+
"""Get overall statistics about the code index."""
|
|
773
|
+
cursor = self.conn.cursor()
|
|
774
|
+
|
|
775
|
+
stats = {}
|
|
776
|
+
|
|
777
|
+
cursor.execute("SELECT COUNT(*) FROM files")
|
|
778
|
+
stats['total_files'] = cursor.fetchone()[0]
|
|
779
|
+
|
|
780
|
+
cursor.execute("SELECT COUNT(*) FROM functions WHERE parent_id IS NULL")
|
|
781
|
+
stats['total_functions'] = cursor.fetchone()[0]
|
|
782
|
+
|
|
783
|
+
cursor.execute("SELECT COUNT(*) FROM functions WHERE parent_id IS NOT NULL")
|
|
784
|
+
stats['total_methods'] = cursor.fetchone()[0]
|
|
785
|
+
|
|
786
|
+
cursor.execute("SELECT COUNT(*) FROM classes WHERE parent_id IS NULL")
|
|
787
|
+
stats['total_classes'] = cursor.fetchone()[0]
|
|
788
|
+
|
|
789
|
+
cursor.execute("SELECT COUNT(*) FROM variables WHERE parent_id IS NULL")
|
|
790
|
+
stats['total_variables'] = cursor.fetchone()[0]
|
|
791
|
+
|
|
792
|
+
cursor.execute("SELECT COUNT(*) FROM type_aliases")
|
|
793
|
+
stats['total_type_aliases'] = cursor.fetchone()[0]
|
|
794
|
+
|
|
795
|
+
cursor.execute("SELECT COUNT(*) FROM structs")
|
|
796
|
+
stats['total_structs'] = cursor.fetchone()[0]
|
|
797
|
+
|
|
798
|
+
cursor.execute("SELECT COUNT(*) FROM interfaces")
|
|
799
|
+
stats['total_interfaces'] = cursor.fetchone()[0]
|
|
800
|
+
|
|
801
|
+
return stats
|
|
802
|
+
|
|
803
|
+
def get_language_statistics(self) -> Dict[str, int]:
|
|
804
|
+
"""Get statistics grouped by language."""
|
|
805
|
+
cursor = self.conn.cursor()
|
|
806
|
+
cursor.execute("SELECT language, COUNT(*) as count FROM files GROUP BY language ORDER BY count DESC")
|
|
807
|
+
return {row['language']: row['count'] for row in cursor.fetchall()}
|
|
808
|
+
|
|
809
|
+
# ==================== Dependency Queries ====================
|
|
810
|
+
|
|
811
|
+
def get_dependencies(self, file_id: Optional[int] = None) -> List[Dependency]:
|
|
812
|
+
"""Get all dependencies, optionally filtered by file."""
|
|
813
|
+
cursor = self.conn.cursor()
|
|
814
|
+
if file_id:
|
|
815
|
+
cursor.execute(
|
|
816
|
+
"SELECT * FROM dependencies WHERE file_id = ? ORDER BY dependency_type, name",
|
|
817
|
+
(file_id,)
|
|
818
|
+
)
|
|
819
|
+
else:
|
|
820
|
+
cursor.execute("SELECT * FROM dependencies ORDER BY dependency_type, name")
|
|
821
|
+
|
|
822
|
+
dependencies = []
|
|
823
|
+
for row in cursor.fetchall():
|
|
824
|
+
file = self.get_file_by_id(row['file_id'])
|
|
825
|
+
if file:
|
|
826
|
+
dependencies.append(Dependency.from_row(row, file.path))
|
|
827
|
+
return dependencies
|
|
828
|
+
|
|
829
|
+
def get_file_imports(self, file_id: int) -> List[Dependency]:
|
|
830
|
+
"""Get all imports for a file."""
|
|
831
|
+
cursor = self.conn.cursor()
|
|
832
|
+
cursor.execute(
|
|
833
|
+
"SELECT * FROM dependencies WHERE file_id = ? AND dependency_type = 'import' ORDER BY name",
|
|
834
|
+
(file_id,)
|
|
835
|
+
)
|
|
836
|
+
|
|
837
|
+
imports = []
|
|
838
|
+
for row in cursor.fetchall():
|
|
839
|
+
file = self.get_file_by_id(row['file_id'])
|
|
840
|
+
if file:
|
|
841
|
+
imports.append(Dependency.from_row(row, file.path))
|
|
842
|
+
return imports
|
|
843
|
+
|
|
844
|
+
def get_external_imports(self, file_id: Optional[int] = None) -> List[Dependency]:
|
|
845
|
+
"""Get external (third-party) imports, optionally filtered by file."""
|
|
846
|
+
cursor = self.conn.cursor()
|
|
847
|
+
if file_id:
|
|
848
|
+
cursor.execute(
|
|
849
|
+
"SELECT * FROM dependencies WHERE file_id = ? AND dependency_type = 'import' AND is_external = 1 ORDER BY name",
|
|
850
|
+
(file_id,)
|
|
851
|
+
)
|
|
852
|
+
else:
|
|
853
|
+
cursor.execute(
|
|
854
|
+
"SELECT * FROM dependencies WHERE dependency_type = 'import' AND is_external = 1 ORDER BY name"
|
|
855
|
+
)
|
|
856
|
+
|
|
857
|
+
imports = []
|
|
858
|
+
for row in cursor.fetchall():
|
|
859
|
+
file = self.get_file_by_id(row['file_id'])
|
|
860
|
+
if file:
|
|
861
|
+
imports.append(Dependency.from_row(row, file.path))
|
|
862
|
+
return imports
|
|
863
|
+
|
|
864
|
+
def get_internal_imports(self, file_id: Optional[int] = None) -> List[Dependency]:
|
|
865
|
+
"""Get internal (project) imports, optionally filtered by file."""
|
|
866
|
+
cursor = self.conn.cursor()
|
|
867
|
+
if file_id:
|
|
868
|
+
cursor.execute(
|
|
869
|
+
"SELECT * FROM dependencies WHERE file_id = ? AND dependency_type = 'import' AND is_external = 0 ORDER BY name",
|
|
870
|
+
(file_id,)
|
|
871
|
+
)
|
|
872
|
+
else:
|
|
873
|
+
cursor.execute(
|
|
874
|
+
"SELECT * FROM dependencies WHERE dependency_type = 'import' AND is_external = 0 ORDER BY name"
|
|
875
|
+
)
|
|
876
|
+
|
|
877
|
+
imports = []
|
|
878
|
+
for row in cursor.fetchall():
|
|
879
|
+
file = self.get_file_by_id(row['file_id'])
|
|
880
|
+
if file:
|
|
881
|
+
imports.append(Dependency.from_row(row, file.path))
|
|
882
|
+
return imports
|
|
883
|
+
|
|
884
|
+
def get_function_calls(self, function_id: int) -> List[Dependency]:
|
|
885
|
+
"""Get all function calls made by a specific function."""
|
|
886
|
+
cursor = self.conn.cursor()
|
|
887
|
+
cursor.execute(
|
|
888
|
+
"SELECT * FROM dependencies WHERE source_function_id = ? AND dependency_type = 'function_call' ORDER BY name",
|
|
889
|
+
(function_id,)
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
calls = []
|
|
893
|
+
for row in cursor.fetchall():
|
|
894
|
+
file = self.get_file_by_id(row['file_id'])
|
|
895
|
+
if file:
|
|
896
|
+
calls.append(Dependency.from_row(row, file.path))
|
|
897
|
+
return calls
|
|
898
|
+
|
|
899
|
+
def get_function_dependencies(self, function_id: int, dependency_type: Optional[str] = None) -> List[Dependency]:
|
|
900
|
+
"""Get all dependencies of a specific function, optionally filtered by type.
|
|
901
|
+
|
|
902
|
+
Args:
|
|
903
|
+
function_id: The ID of the function to query
|
|
904
|
+
dependency_type: Optional filter for dependency type. Can be:
|
|
905
|
+
- 'import': Module imports
|
|
906
|
+
- 'function_call': Function calls
|
|
907
|
+
- 'method_call': Method calls
|
|
908
|
+
- 'class_reference': Class references/instantiations
|
|
909
|
+
- 'variable_reference': Variable references
|
|
910
|
+
- 'module_reference': Module references
|
|
911
|
+
If None, returns all dependency types.
|
|
912
|
+
|
|
913
|
+
Returns:
|
|
914
|
+
List of Dependency objects with precise location information
|
|
915
|
+
"""
|
|
916
|
+
cursor = self.conn.cursor()
|
|
917
|
+
if dependency_type:
|
|
918
|
+
cursor.execute(
|
|
919
|
+
"SELECT * FROM dependencies WHERE source_function_id = ? AND dependency_type = ? ORDER BY name",
|
|
920
|
+
(function_id, dependency_type)
|
|
921
|
+
)
|
|
922
|
+
else:
|
|
923
|
+
cursor.execute(
|
|
924
|
+
"SELECT * FROM dependencies WHERE source_function_id = ? ORDER BY dependency_type, name",
|
|
925
|
+
(function_id,)
|
|
926
|
+
)
|
|
927
|
+
|
|
928
|
+
dependencies = []
|
|
929
|
+
for row in cursor.fetchall():
|
|
930
|
+
file = self.get_file_by_id(row['file_id'])
|
|
931
|
+
if file:
|
|
932
|
+
dependencies.append(Dependency.from_row(row, file.path))
|
|
933
|
+
return dependencies
|
|
934
|
+
|
|
935
|
+
def get_function_method_calls(self, function_id: int) -> List[Dependency]:
|
|
936
|
+
"""Get all method calls made by a specific function."""
|
|
937
|
+
cursor = self.conn.cursor()
|
|
938
|
+
cursor.execute(
|
|
939
|
+
"SELECT * FROM dependencies WHERE source_function_id = ? AND dependency_type = 'method_call' ORDER BY name",
|
|
940
|
+
(function_id,)
|
|
941
|
+
)
|
|
942
|
+
|
|
943
|
+
calls = []
|
|
944
|
+
for row in cursor.fetchall():
|
|
945
|
+
file = self.get_file_by_id(row['file_id'])
|
|
946
|
+
if file:
|
|
947
|
+
calls.append(Dependency.from_row(row, file.path))
|
|
948
|
+
return calls
|
|
949
|
+
|
|
950
|
+
def get_function_class_references(self, function_id: int) -> List[Dependency]:
|
|
951
|
+
"""Get all class references made by a specific function."""
|
|
952
|
+
cursor = self.conn.cursor()
|
|
953
|
+
cursor.execute(
|
|
954
|
+
"SELECT * FROM dependencies WHERE source_function_id = ? AND dependency_type = 'class_reference' ORDER BY name",
|
|
955
|
+
(function_id,)
|
|
956
|
+
)
|
|
957
|
+
|
|
958
|
+
refs = []
|
|
959
|
+
for row in cursor.fetchall():
|
|
960
|
+
file = self.get_file_by_id(row['file_id'])
|
|
961
|
+
if file:
|
|
962
|
+
refs.append(Dependency.from_row(row, file.path))
|
|
963
|
+
return refs
|
|
964
|
+
|
|
965
|
+
def get_function_variable_references(self, function_id: int) -> List[Dependency]:
|
|
966
|
+
"""Get all variable references made by a specific function."""
|
|
967
|
+
cursor = self.conn.cursor()
|
|
968
|
+
cursor.execute(
|
|
969
|
+
"SELECT * FROM dependencies WHERE source_function_id = ? AND dependency_type = 'variable_reference' ORDER BY name",
|
|
970
|
+
(function_id,)
|
|
971
|
+
)
|
|
972
|
+
|
|
973
|
+
refs = []
|
|
974
|
+
for row in cursor.fetchall():
|
|
975
|
+
file = self.get_file_by_id(row['file_id'])
|
|
976
|
+
if file:
|
|
977
|
+
refs.append(Dependency.from_row(row, file.path))
|
|
978
|
+
return refs
|
|
979
|
+
|
|
980
|
+
def get_function_dependencies_by_name(self, function_name: str, file_id: Optional[int] = None, dependency_type: Optional[str] = None) -> List[Dependency]:
|
|
981
|
+
"""Get dependencies for a function by name, optionally filtered by file and dependency type.
|
|
982
|
+
|
|
983
|
+
Args:
|
|
984
|
+
function_name: The name of the function to query
|
|
985
|
+
file_id: Optional file ID to disambiguate functions with the same name
|
|
986
|
+
dependency_type: Optional filter for dependency type
|
|
987
|
+
|
|
988
|
+
Returns:
|
|
989
|
+
List of Dependency objects with precise location information
|
|
990
|
+
"""
|
|
991
|
+
functions = self.get_function_by_name(function_name, file_id)
|
|
992
|
+
if not functions:
|
|
993
|
+
return []
|
|
994
|
+
|
|
995
|
+
func = functions[0]
|
|
996
|
+
return self.get_function_dependencies(func.id, dependency_type)
|
|
997
|
+
|
|
998
|
+
def get_function_dependencies_grouped(self, function_id: int) -> Dict[str, List[Dependency]]:
|
|
999
|
+
"""Get all dependencies of a function grouped by type.
|
|
1000
|
+
|
|
1001
|
+
Returns a dictionary with keys:
|
|
1002
|
+
- 'function_call': Function calls
|
|
1003
|
+
- 'method_call': Method calls
|
|
1004
|
+
- 'class_reference': Class references
|
|
1005
|
+
- 'variable_reference': Variable references
|
|
1006
|
+
- 'import': Module imports (if any)
|
|
1007
|
+
"""
|
|
1008
|
+
all_deps = self.get_function_dependencies(function_id)
|
|
1009
|
+
grouped = {
|
|
1010
|
+
'function_call': [],
|
|
1011
|
+
'method_call': [],
|
|
1012
|
+
'class_reference': [],
|
|
1013
|
+
'variable_reference': [],
|
|
1014
|
+
'import': [],
|
|
1015
|
+
'module_reference': []
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
for dep in all_deps:
|
|
1019
|
+
if dep.dependency_type in grouped:
|
|
1020
|
+
grouped[dep.dependency_type].append(dep)
|
|
1021
|
+
|
|
1022
|
+
return grouped
|
|
1023
|
+
|
|
1024
|
+
def get_file_function_calls(self, file_id: int) -> List[Dependency]:
|
|
1025
|
+
"""Get all function calls in a file."""
|
|
1026
|
+
cursor = self.conn.cursor()
|
|
1027
|
+
cursor.execute(
|
|
1028
|
+
"SELECT * FROM dependencies WHERE file_id = ? AND dependency_type = 'function_call' ORDER BY name",
|
|
1029
|
+
(file_id,)
|
|
1030
|
+
)
|
|
1031
|
+
|
|
1032
|
+
calls = []
|
|
1033
|
+
for row in cursor.fetchall():
|
|
1034
|
+
file = self.get_file_by_id(row['file_id'])
|
|
1035
|
+
if file:
|
|
1036
|
+
calls.append(Dependency.from_row(row, file.path))
|
|
1037
|
+
return calls
|
|
1038
|
+
|
|
1039
|
+
|
|
1040
|
+
class DescriptionMixin:
|
|
1041
|
+
"""Mixin providing description update methods for code index entities."""
|
|
1042
|
+
|
|
1043
|
+
def set_function_description(self, func_id: int, description: str) -> bool:
|
|
1044
|
+
"""Set description for a function by ID."""
|
|
1045
|
+
cursor = self.conn.cursor()
|
|
1046
|
+
cursor.execute(
|
|
1047
|
+
"UPDATE functions SET description = ? WHERE id = ?",
|
|
1048
|
+
(description, func_id)
|
|
1049
|
+
)
|
|
1050
|
+
self.conn.commit()
|
|
1051
|
+
return cursor.rowcount > 0
|
|
1052
|
+
|
|
1053
|
+
def set_function_description_by_name(self, name: str, description: str, file_path: Optional[str] = None) -> bool:
|
|
1054
|
+
"""Set description for a function by name."""
|
|
1055
|
+
cursor = self.conn.cursor()
|
|
1056
|
+
if file_path:
|
|
1057
|
+
cursor.execute(
|
|
1058
|
+
"""UPDATE functions SET description = ?
|
|
1059
|
+
WHERE name = ? AND file_id = (SELECT id FROM files WHERE path = ?)""",
|
|
1060
|
+
(description, name, file_path)
|
|
1061
|
+
)
|
|
1062
|
+
else:
|
|
1063
|
+
cursor.execute(
|
|
1064
|
+
"UPDATE functions SET description = ? WHERE name = ?",
|
|
1065
|
+
(description, name)
|
|
1066
|
+
)
|
|
1067
|
+
self.conn.commit()
|
|
1068
|
+
return cursor.rowcount > 0
|
|
1069
|
+
|
|
1070
|
+
def set_method_description(self, method_id: int, description: str) -> bool:
|
|
1071
|
+
"""Set description for a method by ID."""
|
|
1072
|
+
return self.set_function_description(method_id, description)
|
|
1073
|
+
|
|
1074
|
+
def set_class_description(self, class_id: int, description: str) -> bool:
|
|
1075
|
+
"""Set description for a class by ID."""
|
|
1076
|
+
cursor = self.conn.cursor()
|
|
1077
|
+
cursor.execute(
|
|
1078
|
+
"UPDATE classes SET description = ? WHERE id = ?",
|
|
1079
|
+
(description, class_id)
|
|
1080
|
+
)
|
|
1081
|
+
self.conn.commit()
|
|
1082
|
+
return cursor.rowcount > 0
|
|
1083
|
+
|
|
1084
|
+
def set_class_description_by_name(self, name: str, description: str, file_path: Optional[str] = None) -> bool:
|
|
1085
|
+
"""Set description for a class by name."""
|
|
1086
|
+
cursor = self.conn.cursor()
|
|
1087
|
+
if file_path:
|
|
1088
|
+
cursor.execute(
|
|
1089
|
+
"""UPDATE classes SET description = ?
|
|
1090
|
+
WHERE name = ? AND file_id = (SELECT id FROM files WHERE path = ?)""",
|
|
1091
|
+
(description, name, file_path)
|
|
1092
|
+
)
|
|
1093
|
+
else:
|
|
1094
|
+
cursor.execute(
|
|
1095
|
+
"UPDATE classes SET description = ? WHERE name = ?",
|
|
1096
|
+
(description, name)
|
|
1097
|
+
)
|
|
1098
|
+
self.conn.commit()
|
|
1099
|
+
return cursor.rowcount > 0
|
|
1100
|
+
|
|
1101
|
+
def set_variable_description(self, var_id: int, description: str) -> bool:
|
|
1102
|
+
"""Set description for a variable by ID."""
|
|
1103
|
+
cursor = self.conn.cursor()
|
|
1104
|
+
cursor.execute(
|
|
1105
|
+
"UPDATE variables SET description = ? WHERE id = ?",
|
|
1106
|
+
(description, var_id)
|
|
1107
|
+
)
|
|
1108
|
+
self.conn.commit()
|
|
1109
|
+
return cursor.rowcount > 0
|
|
1110
|
+
|
|
1111
|
+
def set_variable_description_by_name(self, name: str, description: str, file_path: Optional[str] = None) -> bool:
|
|
1112
|
+
"""Set description for a variable by name."""
|
|
1113
|
+
cursor = self.conn.cursor()
|
|
1114
|
+
if file_path:
|
|
1115
|
+
cursor.execute(
|
|
1116
|
+
"""UPDATE variables SET description = ?
|
|
1117
|
+
WHERE name = ? AND file_id = (SELECT id FROM files WHERE path = ?)""",
|
|
1118
|
+
(description, name, file_path)
|
|
1119
|
+
)
|
|
1120
|
+
else:
|
|
1121
|
+
cursor.execute(
|
|
1122
|
+
"UPDATE variables SET description = ? WHERE name = ?",
|
|
1123
|
+
(description, name)
|
|
1124
|
+
)
|
|
1125
|
+
self.conn.commit()
|
|
1126
|
+
return cursor.rowcount > 0
|
|
1127
|
+
|
|
1128
|
+
def set_type_alias_description(self, alias_id: int, description: str) -> bool:
|
|
1129
|
+
"""Set description for a type alias by ID."""
|
|
1130
|
+
cursor = self.conn.cursor()
|
|
1131
|
+
cursor.execute(
|
|
1132
|
+
"UPDATE type_aliases SET description = ? WHERE id = ?",
|
|
1133
|
+
(description, alias_id)
|
|
1134
|
+
)
|
|
1135
|
+
self.conn.commit()
|
|
1136
|
+
return cursor.rowcount > 0
|
|
1137
|
+
|
|
1138
|
+
def set_type_alias_description_by_name(self, name: str, description: str, file_path: Optional[str] = None) -> bool:
|
|
1139
|
+
"""Set description for a type alias by name."""
|
|
1140
|
+
cursor = self.conn.cursor()
|
|
1141
|
+
if file_path:
|
|
1142
|
+
cursor.execute(
|
|
1143
|
+
"""UPDATE type_aliases SET description = ?
|
|
1144
|
+
WHERE name = ? AND file_id = (SELECT id FROM files WHERE path = ?)""",
|
|
1145
|
+
(description, name, file_path)
|
|
1146
|
+
)
|
|
1147
|
+
else:
|
|
1148
|
+
cursor.execute(
|
|
1149
|
+
"UPDATE type_aliases SET description = ? WHERE name = ?",
|
|
1150
|
+
(description, name)
|
|
1151
|
+
)
|
|
1152
|
+
self.conn.commit()
|
|
1153
|
+
return cursor.rowcount > 0
|
|
1154
|
+
|
|
1155
|
+
def set_struct_description(self, struct_id: int, description: str) -> bool:
|
|
1156
|
+
"""Set description for a struct by ID."""
|
|
1157
|
+
cursor = self.conn.cursor()
|
|
1158
|
+
cursor.execute(
|
|
1159
|
+
"UPDATE structs SET description = ? WHERE id = ?",
|
|
1160
|
+
(description, struct_id)
|
|
1161
|
+
)
|
|
1162
|
+
self.conn.commit()
|
|
1163
|
+
return cursor.rowcount > 0
|
|
1164
|
+
|
|
1165
|
+
def set_struct_description_by_name(self, name: str, description: str, file_path: Optional[str] = None) -> bool:
|
|
1166
|
+
"""Set description for a struct by name."""
|
|
1167
|
+
cursor = self.conn.cursor()
|
|
1168
|
+
if file_path:
|
|
1169
|
+
cursor.execute(
|
|
1170
|
+
"""UPDATE structs SET description = ?
|
|
1171
|
+
WHERE name = ? AND file_id = (SELECT id FROM files WHERE path = ?)""",
|
|
1172
|
+
(description, name, file_path)
|
|
1173
|
+
)
|
|
1174
|
+
else:
|
|
1175
|
+
cursor.execute(
|
|
1176
|
+
"UPDATE structs SET description = ? WHERE name = ?",
|
|
1177
|
+
(description, name)
|
|
1178
|
+
)
|
|
1179
|
+
self.conn.commit()
|
|
1180
|
+
return cursor.rowcount > 0
|
|
1181
|
+
|
|
1182
|
+
def set_interface_description(self, interface_id: int, description: str) -> bool:
|
|
1183
|
+
"""Set description for an interface by ID."""
|
|
1184
|
+
cursor = self.conn.cursor()
|
|
1185
|
+
cursor.execute(
|
|
1186
|
+
"UPDATE interfaces SET description = ? WHERE id = ?",
|
|
1187
|
+
(description, interface_id)
|
|
1188
|
+
)
|
|
1189
|
+
self.conn.commit()
|
|
1190
|
+
return cursor.rowcount > 0
|
|
1191
|
+
|
|
1192
|
+
def set_enum_description(self, enum_id: int, description: str) -> bool:
|
|
1193
|
+
"""Set description for an enum by ID."""
|
|
1194
|
+
cursor = self.conn.cursor()
|
|
1195
|
+
cursor.execute(
|
|
1196
|
+
"UPDATE enums SET description = ? WHERE id = ?",
|
|
1197
|
+
(description, enum_id)
|
|
1198
|
+
)
|
|
1199
|
+
self.conn.commit()
|
|
1200
|
+
return cursor.rowcount > 0
|
|
1201
|
+
|
|
1202
|
+
def set_enum_description_by_name(self, name: str, description: str, file_path: Optional[str] = None) -> bool:
|
|
1203
|
+
"""Set description for an enum by name."""
|
|
1204
|
+
cursor = self.conn.cursor()
|
|
1205
|
+
if file_path:
|
|
1206
|
+
cursor.execute(
|
|
1207
|
+
"""UPDATE enums SET description = ?
|
|
1208
|
+
WHERE name = ? AND file_id = (SELECT id FROM files WHERE path = ?)""",
|
|
1209
|
+
(description, name, file_path)
|
|
1210
|
+
)
|
|
1211
|
+
else:
|
|
1212
|
+
cursor.execute(
|
|
1213
|
+
"UPDATE enums SET description = ? WHERE name = ?",
|
|
1214
|
+
(description, name)
|
|
1215
|
+
)
|
|
1216
|
+
self.conn.commit()
|
|
1217
|
+
return cursor.rowcount > 0
|
|
1218
|
+
|
|
1219
|
+
def set_interface_description_by_name(self, name: str, description: str, file_path: Optional[str] = None) -> bool:
|
|
1220
|
+
"""Set description for an interface by name."""
|
|
1221
|
+
cursor = self.conn.cursor()
|
|
1222
|
+
if file_path:
|
|
1223
|
+
cursor.execute(
|
|
1224
|
+
"""UPDATE interfaces SET description = ?
|
|
1225
|
+
WHERE name = ? AND file_id = (SELECT id FROM files WHERE path = ?)""",
|
|
1226
|
+
(description, name, file_path)
|
|
1227
|
+
)
|
|
1228
|
+
else:
|
|
1229
|
+
cursor.execute(
|
|
1230
|
+
"UPDATE interfaces SET description = ? WHERE name = ?",
|
|
1231
|
+
(description, name)
|
|
1232
|
+
)
|
|
1233
|
+
self.conn.commit()
|
|
1234
|
+
return cursor.rowcount > 0
|
|
1235
|
+
|
|
1236
|
+
def set_symbol_description(self, symbol_type: str, symbol_id: int, description: str) -> bool:
|
|
1237
|
+
"""Set description for any symbol type by ID."""
|
|
1238
|
+
dispatch = {
|
|
1239
|
+
'function': self.set_function_description,
|
|
1240
|
+
'method': self.set_method_description,
|
|
1241
|
+
'class': self.set_class_description,
|
|
1242
|
+
'variable': self.set_variable_description,
|
|
1243
|
+
'type_alias': self.set_type_alias_description,
|
|
1244
|
+
'struct': self.set_struct_description,
|
|
1245
|
+
'interface': self.set_interface_description,
|
|
1246
|
+
'enum': self.set_enum_description,
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
if symbol_type not in dispatch:
|
|
1250
|
+
raise ValueError(f"Unknown symbol type: {symbol_type}. Must be one of: {list(dispatch.keys())}")
|
|
1251
|
+
|
|
1252
|
+
return dispatch[symbol_type](symbol_id, description)
|
|
1253
|
+
|
|
1254
|
+
def get_symbol_description(self, symbol_type: str, symbol_id: int) -> Optional[str]:
|
|
1255
|
+
"""Get description for any symbol type by ID."""
|
|
1256
|
+
cursor = self.conn.cursor()
|
|
1257
|
+
table_map = {
|
|
1258
|
+
'function': 'functions',
|
|
1259
|
+
'method': 'functions',
|
|
1260
|
+
'class': 'classes',
|
|
1261
|
+
'variable': 'variables',
|
|
1262
|
+
'type_alias': 'type_aliases',
|
|
1263
|
+
'struct': 'structs',
|
|
1264
|
+
'interface': 'interfaces',
|
|
1265
|
+
'enum': 'enums',
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
if symbol_type not in table_map:
|
|
1269
|
+
raise ValueError(f"Unknown symbol type: {symbol_type}. Must be one of: {list(table_map.keys())}")
|
|
1270
|
+
|
|
1271
|
+
table = table_map[symbol_type]
|
|
1272
|
+
cursor.execute(f"SELECT description FROM {table} WHERE id = ?", (symbol_id,))
|
|
1273
|
+
row = cursor.fetchone()
|
|
1274
|
+
return row['description'] if row else None
|
|
1275
|
+
|
|
1276
|
+
def get_symbol_description_by_name(self, symbol_type: str, name: str, file_path: Optional[str] = None) -> Optional[str]:
|
|
1277
|
+
"""Get description for a symbol by name and type."""
|
|
1278
|
+
cursor = self.conn.cursor()
|
|
1279
|
+
table_map = {
|
|
1280
|
+
'function': 'functions',
|
|
1281
|
+
'method': 'functions',
|
|
1282
|
+
'class': 'classes',
|
|
1283
|
+
'variable': 'variables',
|
|
1284
|
+
'type_alias': 'type_aliases',
|
|
1285
|
+
'struct': 'structs',
|
|
1286
|
+
'interface': 'interfaces',
|
|
1287
|
+
'enum': 'enums',
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
if symbol_type not in table_map:
|
|
1291
|
+
raise ValueError(f"Unknown symbol type: {symbol_type}. Must be one of: {list(table_map.keys())}")
|
|
1292
|
+
|
|
1293
|
+
table = table_map[symbol_type]
|
|
1294
|
+
if file_path:
|
|
1295
|
+
cursor.execute(
|
|
1296
|
+
f"SELECT description FROM {table} WHERE name = ? AND file_id = (SELECT id FROM files WHERE path = ?)",
|
|
1297
|
+
(name, file_path)
|
|
1298
|
+
)
|
|
1299
|
+
else:
|
|
1300
|
+
cursor.execute(f"SELECT description FROM {table} WHERE name = ?", (name,))
|
|
1301
|
+
row = cursor.fetchone()
|
|
1302
|
+
return row['description'] if row else None
|
|
1303
|
+
|
|
1304
|
+
def search_descriptions(self, query: str, symbol_types: Optional[List[str]] = None, limit: int = 50) -> List[Dict[str, Any]]:
|
|
1305
|
+
"""Search for symbols by description content.
|
|
1306
|
+
|
|
1307
|
+
Args:
|
|
1308
|
+
query: Search query string (searches description field using SQL LIKE)
|
|
1309
|
+
symbol_types: Optional list of symbol types to search (e.g., ['function', 'class']).
|
|
1310
|
+
If None, searches all symbol types.
|
|
1311
|
+
limit: Maximum number of results to return
|
|
1312
|
+
|
|
1313
|
+
Returns:
|
|
1314
|
+
List of dicts with keys: type, name, file_path, description
|
|
1315
|
+
"""
|
|
1316
|
+
cursor = self.conn.cursor()
|
|
1317
|
+
like_pattern = f"%{query}%"
|
|
1318
|
+
results = []
|
|
1319
|
+
|
|
1320
|
+
if symbol_types is None:
|
|
1321
|
+
symbol_types = ['function', 'class', 'variable', 'type_alias', 'struct', 'interface', 'enum']
|
|
1322
|
+
|
|
1323
|
+
table_map = {
|
|
1324
|
+
'function': ('functions', 'function'),
|
|
1325
|
+
'method': ('functions', 'method'),
|
|
1326
|
+
'class': ('classes', 'class'),
|
|
1327
|
+
'variable': ('variables', 'variable'),
|
|
1328
|
+
'type_alias': ('type_aliases', 'type_alias'),
|
|
1329
|
+
'struct': ('structs', 'struct'),
|
|
1330
|
+
'interface': ('interfaces', 'interface'),
|
|
1331
|
+
'enum': ('enums', 'enum'),
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
for symbol_type in symbol_types:
|
|
1335
|
+
if symbol_type not in table_map:
|
|
1336
|
+
continue
|
|
1337
|
+
|
|
1338
|
+
table, label = table_map[symbol_type]
|
|
1339
|
+
cursor.execute(
|
|
1340
|
+
f"SELECT name, file_id, description FROM {table} WHERE description LIKE ? LIMIT ?",
|
|
1341
|
+
(like_pattern, limit)
|
|
1342
|
+
)
|
|
1343
|
+
|
|
1344
|
+
for row in cursor.fetchall():
|
|
1345
|
+
file = self.get_file_by_id(row['file_id'])
|
|
1346
|
+
if file:
|
|
1347
|
+
results.append({
|
|
1348
|
+
'type': label,
|
|
1349
|
+
'name': row['name'],
|
|
1350
|
+
'file_path': file.path,
|
|
1351
|
+
'description': row['description']
|
|
1352
|
+
})
|
|
1353
|
+
|
|
1354
|
+
return results[:limit]
|
|
1355
|
+
|
|
1356
|
+
def get_undocumented_symbols(self, symbol_types: Optional[List[str]] = None, file_id: Optional[int] = None) -> Dict[str, List[Dict[str, Any]]]:
|
|
1357
|
+
"""Get symbols that have no description.
|
|
1358
|
+
|
|
1359
|
+
Args:
|
|
1360
|
+
symbol_types: Optional list of symbol types to check (e.g., ['function', 'class']).
|
|
1361
|
+
If None, checks all symbol types.
|
|
1362
|
+
file_id: Optional file ID to filter by file
|
|
1363
|
+
|
|
1364
|
+
Returns:
|
|
1365
|
+
Dict with symbol types as keys and lists of symbol info as values
|
|
1366
|
+
"""
|
|
1367
|
+
cursor = self.conn.cursor()
|
|
1368
|
+
undocumented = {}
|
|
1369
|
+
|
|
1370
|
+
if symbol_types is None:
|
|
1371
|
+
symbol_types = ['function', 'class', 'variable', 'type_alias', 'struct', 'interface', 'enum']
|
|
1372
|
+
|
|
1373
|
+
table_map = {
|
|
1374
|
+
'function': ('functions', 'function', "parent_id IS NULL"),
|
|
1375
|
+
'method': ('functions', 'method', "parent_id IS NOT NULL AND parent_type = 'class'"),
|
|
1376
|
+
'class': ('classes', 'class', "parent_id IS NULL"),
|
|
1377
|
+
'variable': ('variables', 'variable', "parent_id IS NULL"),
|
|
1378
|
+
'type_alias': ('type_aliases', 'type_alias', "1=1"),
|
|
1379
|
+
'struct': ('structs', 'struct', "1=1"),
|
|
1380
|
+
'interface': ('interfaces', 'interface', "1=1"),
|
|
1381
|
+
'enum': ('enums', 'enum', "1=1"),
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
for symbol_type in symbol_types:
|
|
1385
|
+
if symbol_type not in table_map:
|
|
1386
|
+
continue
|
|
1387
|
+
|
|
1388
|
+
table, label, extra_condition = table_map[symbol_type]
|
|
1389
|
+
conditions = ["(description IS NULL OR description = '')", extra_condition]
|
|
1390
|
+
params = []
|
|
1391
|
+
|
|
1392
|
+
if file_id is not None:
|
|
1393
|
+
conditions.append("file_id = ?")
|
|
1394
|
+
params.append(file_id)
|
|
1395
|
+
|
|
1396
|
+
where_clause = " AND ".join(conditions)
|
|
1397
|
+
query = f"SELECT name, file_id FROM {table} WHERE {where_clause} ORDER BY name"
|
|
1398
|
+
cursor.execute(query, params)
|
|
1399
|
+
|
|
1400
|
+
symbols = []
|
|
1401
|
+
for row in cursor.fetchall():
|
|
1402
|
+
file = self.get_file_by_id(row['file_id'])
|
|
1403
|
+
if file:
|
|
1404
|
+
symbols.append({
|
|
1405
|
+
'name': row['name'],
|
|
1406
|
+
'file_path': file.path
|
|
1407
|
+
})
|
|
1408
|
+
|
|
1409
|
+
if symbols:
|
|
1410
|
+
undocumented[label] = symbols
|
|
1411
|
+
|
|
1412
|
+
return undocumented
|
|
1413
|
+
|
|
1414
|
+
# ==================== Frontend Source-Location Lookup ====================
|
|
1415
|
+
|
|
1416
|
+
def lookup_frontend_entity(self, file_path: str, line: int, column: int,
|
|
1417
|
+
include_backend: bool = False) -> List[Dict[str, Any]]:
|
|
1418
|
+
"""Look up the most specific frontend entity at a source location.
|
|
1419
|
+
|
|
1420
|
+
Args:
|
|
1421
|
+
file_path: Project-relative path of the file.
|
|
1422
|
+
line: 1-indexed line number.
|
|
1423
|
+
column: 0-indexed column number.
|
|
1424
|
+
include_backend: If True, also search functions/classes.
|
|
1425
|
+
|
|
1426
|
+
Returns:
|
|
1427
|
+
List of entity dicts sorted by narrowest range first.
|
|
1428
|
+
Each dict contains: entity_type, entity_id, file_id, file_path,
|
|
1429
|
+
source_range, name, and any extra columns from the source table.
|
|
1430
|
+
"""
|
|
1431
|
+
from indexing.frontend.location_lookup import lookup_entity_at_location
|
|
1432
|
+
|
|
1433
|
+
matches = lookup_entity_at_location(
|
|
1434
|
+
self.conn, file_path, line, column, include_backend
|
|
1435
|
+
)
|
|
1436
|
+
return [m.to_dict() for m in matches]
|
|
1437
|
+
|
|
1438
|
+
# ==================== Frontend Graph Traversal (Phase 8) ====================
|
|
1439
|
+
|
|
1440
|
+
def traverse_render_graph(self, component_id: int, direction: str = "children",
|
|
1441
|
+
max_depth: int = 10) -> Dict[str, Any]:
|
|
1442
|
+
"""Traverse the component render graph (children or parents)."""
|
|
1443
|
+
from indexing.frontend.graph import traverse_render_graph as _traverse
|
|
1444
|
+
return _traverse(self.conn, component_id, direction, max_depth)
|
|
1445
|
+
|
|
1446
|
+
def traverse_markup_tree(self, element_id: int, direction: str = "children",
|
|
1447
|
+
max_depth: int = 10) -> Dict[str, Any]:
|
|
1448
|
+
"""Traverse the markup element tree (children or parents)."""
|
|
1449
|
+
from indexing.frontend.graph import traverse_markup_tree as _traverse
|
|
1450
|
+
return _traverse(self.conn, element_id, direction, max_depth)
|
|
1451
|
+
|
|
1452
|
+
def traverse_style_graph(self, selector_id: int, direction: str = "using_elements",
|
|
1453
|
+
max_depth: int = 10) -> Dict[str, Any]:
|
|
1454
|
+
"""Traverse the style selector ↔ element graph."""
|
|
1455
|
+
from indexing.frontend.graph import traverse_style_graph as _traverse
|
|
1456
|
+
return _traverse(self.conn, selector_id, direction, max_depth)
|
|
1457
|
+
|
|
1458
|
+
def traverse_event_graph(self, element_id: int) -> Dict[str, Any]:
|
|
1459
|
+
"""Traverse element → event handlers → handler symbols."""
|
|
1460
|
+
from indexing.frontend.graph import traverse_event_graph as _traverse
|
|
1461
|
+
return _traverse(self.conn, element_id)
|
|
1462
|
+
|
|
1463
|
+
def traverse_binding_graph(self, element_id: int) -> Dict[str, Any]:
|
|
1464
|
+
"""Traverse element → bindings → referenced expressions."""
|
|
1465
|
+
from indexing.frontend.graph import traverse_binding_graph as _traverse
|
|
1466
|
+
return _traverse(self.conn, element_id)
|
|
1467
|
+
|
|
1468
|
+
def traverse_component_to_code(self, component_id: int,
|
|
1469
|
+
max_depth: int = 5) -> Dict[str, Any]:
|
|
1470
|
+
"""Link a component to its implementation function/class and call tree."""
|
|
1471
|
+
from indexing.frontend.graph import traverse_component_to_code as _traverse
|
|
1472
|
+
return _traverse(self.conn, component_id, max_depth)
|
|
1473
|
+
|
|
1474
|
+
def traverse_full_frontend(self, component_id: int,
|
|
1475
|
+
max_depth: int = 10) -> Dict[str, Any]:
|
|
1476
|
+
"""Combined traversal: render + markup + events + bindings + styles."""
|
|
1477
|
+
from indexing.frontend.graph import traverse_full_frontend as _traverse
|
|
1478
|
+
return _traverse(self.conn, component_id, max_depth)
|
|
1479
|
+
|
|
1480
|
+
# ==================== Frontend Runtime Resolution (Phase 9) ====================
|
|
1481
|
+
|
|
1482
|
+
def resolve_runtime_element(self, metadata: Dict[str, Any]) -> Dict[str, Any]:
|
|
1483
|
+
"""Resolve browser/runtime element metadata to source semantic entities.
|
|
1484
|
+
|
|
1485
|
+
Args:
|
|
1486
|
+
metadata: Dict with keys like source_file, source_line, source_column,
|
|
1487
|
+
component_name, component_ancestry, dom_tag, element_id,
|
|
1488
|
+
classes, attributes, text, dom_ancestry.
|
|
1489
|
+
|
|
1490
|
+
Returns:
|
|
1491
|
+
Dict with candidates (sorted by confidence), ambiguity_explanation,
|
|
1492
|
+
and best_confidence.
|
|
1493
|
+
"""
|
|
1494
|
+
from indexing.frontend.runtime_resolver import resolve_runtime_element as _resolve
|
|
1495
|
+
project_root = getattr(self, 'root_dir', None)
|
|
1496
|
+
return _resolve(self.conn, metadata, project_root=project_root)
|
|
1497
|
+
|
|
1498
|
+
# ==================== Frontend Diagnostics (Phase 13) ====================
|
|
1499
|
+
|
|
1500
|
+
def get_frontend_diagnostics(self, file_id: int) -> List[Dict[str, Any]]:
|
|
1501
|
+
"""Get all diagnostics for a frontend file.
|
|
1502
|
+
|
|
1503
|
+
Args:
|
|
1504
|
+
file_id: ID of the file to get diagnostics for.
|
|
1505
|
+
|
|
1506
|
+
Returns:
|
|
1507
|
+
List of diagnostic dicts with keys: id, file_id, diagnostic_type,
|
|
1508
|
+
severity, message, source_range.
|
|
1509
|
+
"""
|
|
1510
|
+
cursor = self.conn.cursor()
|
|
1511
|
+
cursor.execute(
|
|
1512
|
+
"""SELECT d.id, d.file_id, d.diagnostic_type, d.severity,
|
|
1513
|
+
d.message, d.source_range, f.path as file_path
|
|
1514
|
+
FROM frontend_diagnostics d
|
|
1515
|
+
JOIN files f ON d.file_id = f.id
|
|
1516
|
+
WHERE d.file_id = ?
|
|
1517
|
+
ORDER BY d.id""",
|
|
1518
|
+
(file_id,)
|
|
1519
|
+
)
|
|
1520
|
+
return [dict(row) for row in cursor.fetchall()]
|
|
1521
|
+
|
|
1522
|
+
def get_diagnostics_by_severity(self, severity: str) -> List[Dict[str, Any]]:
|
|
1523
|
+
"""Get all diagnostics matching a severity level.
|
|
1524
|
+
|
|
1525
|
+
Args:
|
|
1526
|
+
severity: One of 'fatal', 'recoverable', 'unresolved', 'unsupported', 'info'.
|
|
1527
|
+
|
|
1528
|
+
Returns:
|
|
1529
|
+
List of diagnostic dicts with keys: id, file_id, diagnostic_type,
|
|
1530
|
+
severity, message, source_range, file_path.
|
|
1531
|
+
"""
|
|
1532
|
+
cursor = self.conn.cursor()
|
|
1533
|
+
cursor.execute(
|
|
1534
|
+
"""SELECT d.id, d.file_id, d.diagnostic_type, d.severity,
|
|
1535
|
+
d.message, d.source_range, f.path as file_path
|
|
1536
|
+
FROM frontend_diagnostics d
|
|
1537
|
+
JOIN files f ON d.file_id = f.id
|
|
1538
|
+
WHERE d.severity = ?
|
|
1539
|
+
ORDER BY d.id""",
|
|
1540
|
+
(severity,)
|
|
1541
|
+
)
|
|
1542
|
+
return [dict(row) for row in cursor.fetchall()]
|