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/parse_worker.py
ADDED
|
@@ -0,0 +1,1082 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Parallel file parsing worker for the code indexer.
|
|
3
|
+
Runs in separate processes to parallelize tree-sitter parsing.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from indexing.language_config import LANGUAGE_CONFIG, get_language_for_extension, get_node_types
|
|
9
|
+
from indexing.node_utils import (
|
|
10
|
+
create_parser,
|
|
11
|
+
extract_imports,
|
|
12
|
+
extract_function_calls,
|
|
13
|
+
extract_class_references,
|
|
14
|
+
extract_variable_references,
|
|
15
|
+
extract_node_text,
|
|
16
|
+
get_node_location,
|
|
17
|
+
count_branches,
|
|
18
|
+
extract_go_type_kind,
|
|
19
|
+
)
|
|
20
|
+
from indexing.extractors import (
|
|
21
|
+
extract_function_info,
|
|
22
|
+
extract_class_info,
|
|
23
|
+
extract_variable_info,
|
|
24
|
+
extract_type_alias_info,
|
|
25
|
+
extract_macro_info,
|
|
26
|
+
extract_struct_info,
|
|
27
|
+
extract_interface_info,
|
|
28
|
+
extract_enum_info,
|
|
29
|
+
extract_go_struct_info,
|
|
30
|
+
extract_go_interface_info
|
|
31
|
+
)
|
|
32
|
+
from indexing.file_utils import read_file_content
|
|
33
|
+
import xxhash
|
|
34
|
+
|
|
35
|
+
# Global parser cache - initialized once per worker process
|
|
36
|
+
_parsers = {}
|
|
37
|
+
|
|
38
|
+
def _get_parser(language):
|
|
39
|
+
"""Get or create a parser for the given language (cached per worker)."""
|
|
40
|
+
if language not in _parsers:
|
|
41
|
+
lang_module = LANGUAGE_CONFIG[language]["language_module"]
|
|
42
|
+
_parsers[language] = create_parser(lang_module) if lang_module else None
|
|
43
|
+
return _parsers[language]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def parse_file(args):
|
|
47
|
+
"""Parse a single file and return extracted symbols as serializable data.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
args: (file_path, root_dir) or (file_path, root_dir, frontend_enabled) tuple
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
dict with extracted symbols, or None if file should be skipped
|
|
54
|
+
"""
|
|
55
|
+
if len(args) == 3:
|
|
56
|
+
file_path, root_dir, frontend_enabled = args
|
|
57
|
+
else:
|
|
58
|
+
file_path, root_dir = args
|
|
59
|
+
frontend_enabled = True
|
|
60
|
+
file_path = Path(file_path)
|
|
61
|
+
root_dir = Path(root_dir)
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
ext = file_path.suffix
|
|
65
|
+
language = get_language_for_extension(ext)
|
|
66
|
+
if not language:
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
source_bytes = read_file_content(file_path)
|
|
70
|
+
content_hash = xxhash.xxh64(source_bytes).hexdigest()
|
|
71
|
+
file_mtime = file_path.stat().st_mtime
|
|
72
|
+
|
|
73
|
+
# Dispatch HTML files to the HTML semantic extractor
|
|
74
|
+
if language == "html":
|
|
75
|
+
from indexing.frontend_config import load_frontend_config
|
|
76
|
+
config = load_frontend_config(str(root_dir))
|
|
77
|
+
if len(source_bytes) > config.max_frontend_file_size:
|
|
78
|
+
return {
|
|
79
|
+
'file_path': str(file_path),
|
|
80
|
+
'language': 'html',
|
|
81
|
+
'content_hash': content_hash,
|
|
82
|
+
'file_mtime': file_mtime,
|
|
83
|
+
'imports': [],
|
|
84
|
+
'functions': [],
|
|
85
|
+
'classes': [],
|
|
86
|
+
'variables': [],
|
|
87
|
+
'type_aliases': [],
|
|
88
|
+
'structs': [],
|
|
89
|
+
'interfaces': [],
|
|
90
|
+
'enums': [],
|
|
91
|
+
'namespaces': [],
|
|
92
|
+
'dependencies': [],
|
|
93
|
+
'markup_elements': [],
|
|
94
|
+
'frontend_events': [],
|
|
95
|
+
'frontend_bindings': [],
|
|
96
|
+
'render_relationships': [],
|
|
97
|
+
'style_selector_matches': [],
|
|
98
|
+
'frontend_diagnostics': [{
|
|
99
|
+
'diagnostic_type': 'file_too_large',
|
|
100
|
+
'severity': 'unsupported',
|
|
101
|
+
'message': f'HTML file is {len(source_bytes)} bytes (threshold: {config.max_frontend_file_size}), skipping',
|
|
102
|
+
}],
|
|
103
|
+
}
|
|
104
|
+
return _parse_html_file(file_path, root_dir, source_bytes, content_hash, file_mtime, config)
|
|
105
|
+
|
|
106
|
+
# Dispatch CSS files to the CSS semantic extractor
|
|
107
|
+
if language == "css":
|
|
108
|
+
from indexing.frontend_config import load_frontend_config
|
|
109
|
+
config = load_frontend_config(str(root_dir))
|
|
110
|
+
return _parse_css_file(file_path, root_dir, source_bytes, content_hash, file_mtime, config)
|
|
111
|
+
|
|
112
|
+
parser = _get_parser(language)
|
|
113
|
+
if parser is None:
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
tree = parser.parse(source_bytes)
|
|
117
|
+
root_node = tree.root_node
|
|
118
|
+
source_code = source_bytes.decode('utf-8', errors='ignore')
|
|
119
|
+
|
|
120
|
+
# Extract imports — pass source_bytes to avoid whole-source re-encoding
|
|
121
|
+
imports = extract_imports(root_node, source_bytes, language, root_dir)
|
|
122
|
+
|
|
123
|
+
# Extract symbols
|
|
124
|
+
functions = []
|
|
125
|
+
classes = []
|
|
126
|
+
variables = []
|
|
127
|
+
type_aliases = []
|
|
128
|
+
structs = []
|
|
129
|
+
interfaces = []
|
|
130
|
+
enums = []
|
|
131
|
+
namespaces = []
|
|
132
|
+
dependencies = []
|
|
133
|
+
|
|
134
|
+
_extract_symbols(
|
|
135
|
+
root_node, source_bytes, language, file_path, root_dir,
|
|
136
|
+
functions, classes, variables, type_aliases, structs, interfaces, enums, namespaces, dependencies
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Extract JSX semantics for JS/TSX files (additive to existing symbols)
|
|
140
|
+
# Skip JSX extraction for plain JavaScript when frontend is disabled
|
|
141
|
+
jsx_data = {}
|
|
142
|
+
if language == "tsx" or (language == "javascript" and frontend_enabled):
|
|
143
|
+
from indexing.frontend_config import load_frontend_config
|
|
144
|
+
jsx_config = load_frontend_config(str(root_dir))
|
|
145
|
+
jsx_data = _extract_jsx_data(source_bytes, language, jsx_config, tree=tree)
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
'file_path': str(file_path),
|
|
149
|
+
'language': language,
|
|
150
|
+
'content_hash': content_hash,
|
|
151
|
+
'file_mtime': file_mtime,
|
|
152
|
+
'imports': imports,
|
|
153
|
+
'functions': functions,
|
|
154
|
+
'classes': classes,
|
|
155
|
+
'variables': variables,
|
|
156
|
+
'type_aliases': type_aliases,
|
|
157
|
+
'structs': structs,
|
|
158
|
+
'interfaces': interfaces,
|
|
159
|
+
'enums': enums,
|
|
160
|
+
'namespaces': namespaces,
|
|
161
|
+
'dependencies': dependencies,
|
|
162
|
+
'frontend_components': jsx_data.get('frontend_components', []),
|
|
163
|
+
'markup_elements': jsx_data.get('markup_elements', []),
|
|
164
|
+
'frontend_events': jsx_data.get('frontend_events', []),
|
|
165
|
+
'frontend_bindings': jsx_data.get('frontend_bindings', []),
|
|
166
|
+
'render_relationships': jsx_data.get('render_relationships', []),
|
|
167
|
+
'style_selector_matches': jsx_data.get('style_selector_matches', []),
|
|
168
|
+
'frontend_diagnostics': jsx_data.get('frontend_diagnostics', []),
|
|
169
|
+
}
|
|
170
|
+
except Exception as e:
|
|
171
|
+
return {'error': str(e), 'file_path': str(file_path)}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _maybe_extract_arrow_function(node, source_code, var_info, functions, dependencies):
|
|
175
|
+
"""Check if a variable declaration assigns an arrow function or function expression.
|
|
176
|
+
|
|
177
|
+
If so, register it as a function in addition to being a variable.
|
|
178
|
+
This captures React components like: const MyComponent = () => { ... }
|
|
179
|
+
and handlers like: const handleClick = (e) => { ... }
|
|
180
|
+
"""
|
|
181
|
+
# Walk the node's children to find variable_declarator → arrow_function/function_expression
|
|
182
|
+
stack = list(node.children)
|
|
183
|
+
while stack:
|
|
184
|
+
child = stack.pop()
|
|
185
|
+
if child.type == "variable_declarator":
|
|
186
|
+
for vc in child.children:
|
|
187
|
+
if vc.type in ("arrow_function", "function_expression"):
|
|
188
|
+
func_info = {
|
|
189
|
+
"type": "function",
|
|
190
|
+
"name": var_info["name"],
|
|
191
|
+
"location": var_info["location"],
|
|
192
|
+
"parameters": _extract_arrow_params(vc, source_code),
|
|
193
|
+
"return_type": None,
|
|
194
|
+
"docstring": None,
|
|
195
|
+
"branch_count": _count_branches_in_node(vc, source_code),
|
|
196
|
+
}
|
|
197
|
+
func_index = len(functions)
|
|
198
|
+
functions.append(func_info)
|
|
199
|
+
_extract_deps(vc, source_code, "javascript", dependencies, func_index)
|
|
200
|
+
return
|
|
201
|
+
# Also push declarator children for deeper search
|
|
202
|
+
stack.extend(child.children)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _extract_arrow_params(func_node, source_code):
|
|
206
|
+
"""Extract parameter names from an arrow_function or function_expression node."""
|
|
207
|
+
params = []
|
|
208
|
+
for child in func_node.children:
|
|
209
|
+
if child.type == "formal_parameters":
|
|
210
|
+
for p in child.children:
|
|
211
|
+
if p.type == "identifier":
|
|
212
|
+
params.append({"name": extract_node_text(p, source_code), "type": None})
|
|
213
|
+
elif p.type == "assignment_pattern":
|
|
214
|
+
# Default parameter: (x = 1)
|
|
215
|
+
for ap in p.children:
|
|
216
|
+
if ap.type == "identifier":
|
|
217
|
+
params.append({"name": extract_node_text(ap, source_code), "type": None})
|
|
218
|
+
break
|
|
219
|
+
elif child.type == "identifier":
|
|
220
|
+
# Single param without parens: x => ...
|
|
221
|
+
params.append({"name": extract_node_text(child, source_code), "type": None})
|
|
222
|
+
return params
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _count_branches_in_node(node, source_code):
|
|
226
|
+
"""Count if/else/switch/for/while/try branches inside a node (non-recursive)."""
|
|
227
|
+
count = 0
|
|
228
|
+
stack = list(node.children)
|
|
229
|
+
while stack:
|
|
230
|
+
child = stack.pop()
|
|
231
|
+
if child.type in ("if_statement", "for_statement", "for_in_statement",
|
|
232
|
+
"while_statement", "switch_statement", "try_statement"):
|
|
233
|
+
count += 1
|
|
234
|
+
stack.extend(child.children)
|
|
235
|
+
return count
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _extract_symbols(root_node, source_code, language, file_path, root_dir,
|
|
239
|
+
functions, classes, variables, type_aliases, structs, interfaces, enums, namespaces, dependencies):
|
|
240
|
+
"""Iteratively extract symbols from a tree-sitter AST using an explicit stack.
|
|
241
|
+
Matches the logic of CodeIndexer._process_node exactly.
|
|
242
|
+
Avoids recursion depth limits on deeply nested ASTs (e.g., Linux kernel C files).
|
|
243
|
+
"""
|
|
244
|
+
node_types = get_node_types(language)
|
|
245
|
+
if not node_types:
|
|
246
|
+
return
|
|
247
|
+
|
|
248
|
+
function_type = node_types.get("function")
|
|
249
|
+
class_type = node_types.get("class")
|
|
250
|
+
struct_type = node_types.get("struct")
|
|
251
|
+
enum_type = node_types.get("enum")
|
|
252
|
+
interface_type = node_types.get("interface")
|
|
253
|
+
assignment_type = node_types.get("assignment")
|
|
254
|
+
type_alias_type = node_types.get("type_alias")
|
|
255
|
+
|
|
256
|
+
# C# 9 top-level statements: collect global_statement nodes into a pseudo-function
|
|
257
|
+
if language == "csharp":
|
|
258
|
+
global_stmts = [child for child in root_node.children if child.type == "global_statement"]
|
|
259
|
+
if global_stmts:
|
|
260
|
+
first = global_stmts[0]
|
|
261
|
+
last = global_stmts[-1]
|
|
262
|
+
pseudo_func = {
|
|
263
|
+
"type": "function",
|
|
264
|
+
"name": "top_level_statements",
|
|
265
|
+
"location": {
|
|
266
|
+
"start_line": first.start_point[0] + 1,
|
|
267
|
+
"start_column": first.start_point[1],
|
|
268
|
+
"end_line": last.end_point[0] + 1,
|
|
269
|
+
"end_column": last.end_point[1],
|
|
270
|
+
"start_byte": first.start_byte,
|
|
271
|
+
"end_byte": last.end_byte,
|
|
272
|
+
},
|
|
273
|
+
"parameters": [],
|
|
274
|
+
"return_type": None,
|
|
275
|
+
"docstring": None,
|
|
276
|
+
"branch_count": 0,
|
|
277
|
+
}
|
|
278
|
+
func_index = len(functions)
|
|
279
|
+
functions.append(pseudo_func)
|
|
280
|
+
# Extract deps from all global statements combined
|
|
281
|
+
for gs in global_stmts:
|
|
282
|
+
_extract_deps(gs, source_code, language, dependencies, func_index)
|
|
283
|
+
# Extract top-level variable declarations
|
|
284
|
+
for gs in global_stmts:
|
|
285
|
+
for child in gs.children:
|
|
286
|
+
if child.type == "local_declaration_statement":
|
|
287
|
+
# C# local_declaration_statement: variable_declaration → variable_declarator → identifier
|
|
288
|
+
var_name = None
|
|
289
|
+
var_type = None
|
|
290
|
+
for vc in child.children:
|
|
291
|
+
if vc.type == "variable_declaration":
|
|
292
|
+
# Extract type from first child (implicit_type or type)
|
|
293
|
+
if vc.children:
|
|
294
|
+
var_type = extract_node_text(vc.children[0], source_code)
|
|
295
|
+
for vd in vc.children:
|
|
296
|
+
if vd.type == "variable_declarator":
|
|
297
|
+
for vdc in vd.children:
|
|
298
|
+
if vdc.type == "identifier":
|
|
299
|
+
var_name = extract_node_text(vdc, source_code)
|
|
300
|
+
break
|
|
301
|
+
break
|
|
302
|
+
break
|
|
303
|
+
if var_name:
|
|
304
|
+
variables.append({
|
|
305
|
+
"type": "variable",
|
|
306
|
+
"name": var_name,
|
|
307
|
+
"location": get_node_location(child),
|
|
308
|
+
"field_type": var_type,
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
# Stack entries: (node, in_function, current_class_id, current_namespace)
|
|
312
|
+
stack = [(root_node, False, None, None)]
|
|
313
|
+
|
|
314
|
+
while stack:
|
|
315
|
+
node, in_function, current_class_id, current_namespace = stack.pop()
|
|
316
|
+
|
|
317
|
+
# Skip global_statement nodes — already handled in C# top-level pre-pass
|
|
318
|
+
if node.type == "global_statement":
|
|
319
|
+
continue
|
|
320
|
+
|
|
321
|
+
# C# namespace declaration: extract name and track as current namespace
|
|
322
|
+
if language == "csharp" and node.type == "namespace_declaration":
|
|
323
|
+
ns_name = None
|
|
324
|
+
for child in node.children:
|
|
325
|
+
if child.type in ("qualified_name", "identifier"):
|
|
326
|
+
ns_name = extract_node_text(child, source_code)
|
|
327
|
+
break
|
|
328
|
+
if ns_name:
|
|
329
|
+
# Build fully qualified namespace name
|
|
330
|
+
if current_namespace:
|
|
331
|
+
full_ns = f"{current_namespace}.{ns_name}"
|
|
332
|
+
else:
|
|
333
|
+
full_ns = ns_name
|
|
334
|
+
namespaces.append({
|
|
335
|
+
"name": full_ns,
|
|
336
|
+
"location": get_node_location(node),
|
|
337
|
+
})
|
|
338
|
+
# Push children with the new namespace context
|
|
339
|
+
children = list(node.children)
|
|
340
|
+
for child in reversed(children):
|
|
341
|
+
stack.append((child, in_function, current_class_id, full_ns))
|
|
342
|
+
continue
|
|
343
|
+
# If we couldn't extract the name, still traverse children
|
|
344
|
+
children = list(node.children)
|
|
345
|
+
for child in reversed(children):
|
|
346
|
+
stack.append((child, in_function, current_class_id, current_namespace))
|
|
347
|
+
continue
|
|
348
|
+
|
|
349
|
+
if function_type and node.type in function_type:
|
|
350
|
+
func_info = extract_function_info(node, source_code, language, class_type)
|
|
351
|
+
if func_info:
|
|
352
|
+
if current_class_id is not None:
|
|
353
|
+
func_info['parent_class_id'] = current_class_id
|
|
354
|
+
func_index = len(functions)
|
|
355
|
+
functions.append(func_info)
|
|
356
|
+
# Dart: function_signature and function_body are siblings.
|
|
357
|
+
# Extract deps from both the signature and its sibling body.
|
|
358
|
+
if language == "dart" and node.parent:
|
|
359
|
+
body = None
|
|
360
|
+
found_self = False
|
|
361
|
+
for sibling in node.parent.children:
|
|
362
|
+
if sibling is node:
|
|
363
|
+
found_self = True
|
|
364
|
+
continue
|
|
365
|
+
if found_self and sibling.type == "function_body":
|
|
366
|
+
body = sibling
|
|
367
|
+
break
|
|
368
|
+
if body:
|
|
369
|
+
_extract_deps(node, source_code, language, dependencies, func_index)
|
|
370
|
+
_extract_deps(body, source_code, language, dependencies, func_index)
|
|
371
|
+
else:
|
|
372
|
+
_extract_deps(node, source_code, language, dependencies, func_index)
|
|
373
|
+
else:
|
|
374
|
+
_extract_deps(node, source_code, language, dependencies, func_index)
|
|
375
|
+
# Push children with in_function=True (reverse order for correct traversal)
|
|
376
|
+
children = list(node.children)
|
|
377
|
+
for child in reversed(children):
|
|
378
|
+
stack.append((child, True, current_class_id, current_namespace))
|
|
379
|
+
continue
|
|
380
|
+
|
|
381
|
+
if class_type and node.type in class_type:
|
|
382
|
+
# Kotlin: class_declaration is used for interfaces and enums too
|
|
383
|
+
# Check for interface/enum keyword children to reclassify
|
|
384
|
+
if language == "kotlin" and node.type == "class_declaration":
|
|
385
|
+
is_interface = False
|
|
386
|
+
is_enum = False
|
|
387
|
+
for child in node.children:
|
|
388
|
+
if child.type == "interface":
|
|
389
|
+
is_interface = True
|
|
390
|
+
break
|
|
391
|
+
if child.type == "enum":
|
|
392
|
+
is_enum = True
|
|
393
|
+
break
|
|
394
|
+
if is_interface:
|
|
395
|
+
iface_info = extract_interface_info(node, source_code, language)
|
|
396
|
+
if iface_info:
|
|
397
|
+
interfaces.append(iface_info)
|
|
398
|
+
continue
|
|
399
|
+
if is_enum:
|
|
400
|
+
enum_info = extract_enum_info(node, source_code, language)
|
|
401
|
+
if enum_info:
|
|
402
|
+
enums.append(enum_info)
|
|
403
|
+
continue
|
|
404
|
+
|
|
405
|
+
class_info = extract_class_info(node, source_code, language)
|
|
406
|
+
if class_info:
|
|
407
|
+
class_id = len(classes)
|
|
408
|
+
class_info['_temp_id'] = class_id
|
|
409
|
+
if current_namespace:
|
|
410
|
+
class_info['namespace'] = current_namespace
|
|
411
|
+
classes.append(class_info)
|
|
412
|
+
_process_class_body(node, source_code, language, class_id,
|
|
413
|
+
functions, classes, variables, dependencies)
|
|
414
|
+
continue
|
|
415
|
+
|
|
416
|
+
if assignment_type and node.type in assignment_type:
|
|
417
|
+
if not in_function:
|
|
418
|
+
var_info = extract_variable_info(node, source_code, language)
|
|
419
|
+
if var_info and var_info["type"] == "variable":
|
|
420
|
+
if current_class_id is not None:
|
|
421
|
+
var_info['parent_class_id'] = current_class_id
|
|
422
|
+
variables.append(var_info)
|
|
423
|
+
# JS/TS: detect arrow functions and function expressions assigned to variables
|
|
424
|
+
if language in ("javascript", "tsx", "typescript"):
|
|
425
|
+
_maybe_extract_arrow_function(node, source_code, var_info, functions, dependencies)
|
|
426
|
+
continue
|
|
427
|
+
|
|
428
|
+
# Rust global variables: const and static items
|
|
429
|
+
if language == "rust" and node.type in ("const_item", "static_item"):
|
|
430
|
+
if not in_function:
|
|
431
|
+
var_info = extract_variable_info(node, source_code, language)
|
|
432
|
+
if var_info and var_info["type"] == "variable":
|
|
433
|
+
variables.append(var_info)
|
|
434
|
+
continue
|
|
435
|
+
|
|
436
|
+
# Rust impl blocks: process methods inside impl_item as class methods
|
|
437
|
+
if language == "rust" and node.type == "impl_item":
|
|
438
|
+
# Extract the type name from the impl block
|
|
439
|
+
type_node = node.child_by_field_name("type")
|
|
440
|
+
impl_type_name = None
|
|
441
|
+
if type_node:
|
|
442
|
+
impl_type_name = extract_node_text(type_node, source_code)
|
|
443
|
+
# Create a pseudo-class for the impl block so methods can reference it
|
|
444
|
+
if impl_type_name:
|
|
445
|
+
impl_id = len(classes)
|
|
446
|
+
classes.append({
|
|
447
|
+
"type": "class",
|
|
448
|
+
"name": impl_type_name,
|
|
449
|
+
"location": get_node_location(node),
|
|
450
|
+
"base_classes": [],
|
|
451
|
+
"docstring": None,
|
|
452
|
+
"methods": [],
|
|
453
|
+
"nested_classes": [],
|
|
454
|
+
"variables": [],
|
|
455
|
+
"_temp_id": impl_id,
|
|
456
|
+
})
|
|
457
|
+
_process_class_body(node, source_code, language, impl_id,
|
|
458
|
+
functions, classes, variables, dependencies)
|
|
459
|
+
continue
|
|
460
|
+
|
|
461
|
+
# Elixir: def/defp/defmacro/defmacrop are function definitions
|
|
462
|
+
if language == "elixir" and node.type == "call":
|
|
463
|
+
# Check if this is a def/defp/defmacro/defmacrop call
|
|
464
|
+
func_name = None
|
|
465
|
+
for child in node.children:
|
|
466
|
+
if child.type == "identifier":
|
|
467
|
+
func_name = extract_node_text(child, source_code)
|
|
468
|
+
break
|
|
469
|
+
if func_name in ("def", "defp", "defmacro", "defmacrop"):
|
|
470
|
+
# Extract the function name from the first argument
|
|
471
|
+
elixir_func_name = None
|
|
472
|
+
for child in node.children:
|
|
473
|
+
if child.type == "arguments":
|
|
474
|
+
for arg in child.children:
|
|
475
|
+
if arg.type == "call":
|
|
476
|
+
# def foo(bar) -> the function name is the identifier inside the call
|
|
477
|
+
for grandchild in arg.children:
|
|
478
|
+
if grandchild.type == "identifier":
|
|
479
|
+
elixir_func_name = extract_node_text(grandchild, source_code)
|
|
480
|
+
break
|
|
481
|
+
elif arg.type == "identifier":
|
|
482
|
+
elixir_func_name = extract_node_text(arg, source_code)
|
|
483
|
+
break
|
|
484
|
+
elif arg.type == "atom":
|
|
485
|
+
# def :foo -> atom function name
|
|
486
|
+
atom_text = extract_node_text(arg, source_code)
|
|
487
|
+
elixir_func_name = atom_text.lstrip(':')
|
|
488
|
+
break
|
|
489
|
+
break
|
|
490
|
+
if elixir_func_name:
|
|
491
|
+
func_info = {
|
|
492
|
+
"type": "function" if func_name in ("def", "defmacro") else "function",
|
|
493
|
+
"name": elixir_func_name,
|
|
494
|
+
"location": get_node_location(node),
|
|
495
|
+
"parameters": [],
|
|
496
|
+
"return_type": None,
|
|
497
|
+
"docstring": None,
|
|
498
|
+
"branch_count": count_branches(node, language, source_code),
|
|
499
|
+
}
|
|
500
|
+
func_index = len(functions)
|
|
501
|
+
functions.append(func_info)
|
|
502
|
+
_extract_deps(node, source_code, language, dependencies, func_index)
|
|
503
|
+
continue
|
|
504
|
+
# defmodule creates a module (treated as a class)
|
|
505
|
+
elif func_name == "defmodule":
|
|
506
|
+
# Extract module name
|
|
507
|
+
module_name = None
|
|
508
|
+
for child in node.children:
|
|
509
|
+
if child.type == "arguments":
|
|
510
|
+
for arg in child.children:
|
|
511
|
+
if arg.type == "alias":
|
|
512
|
+
module_name = extract_node_text(arg, source_code)
|
|
513
|
+
break
|
|
514
|
+
break
|
|
515
|
+
if module_name:
|
|
516
|
+
class_id = len(classes)
|
|
517
|
+
classes.append({
|
|
518
|
+
"type": "class",
|
|
519
|
+
"name": module_name,
|
|
520
|
+
"location": get_node_location(node),
|
|
521
|
+
"base_classes": [],
|
|
522
|
+
"docstring": None,
|
|
523
|
+
"methods": [],
|
|
524
|
+
"nested_classes": [],
|
|
525
|
+
"variables": [],
|
|
526
|
+
"_temp_id": class_id,
|
|
527
|
+
})
|
|
528
|
+
# Process the do block as class body
|
|
529
|
+
for child in node.children:
|
|
530
|
+
if child.type == "do_block":
|
|
531
|
+
for stmt in child.children:
|
|
532
|
+
if stmt.type == "stab_clause":
|
|
533
|
+
# Process stab_clause children for inner def/defp
|
|
534
|
+
stack.append((stmt, False, class_id, current_namespace))
|
|
535
|
+
else:
|
|
536
|
+
stack.append((stmt, False, class_id, current_namespace))
|
|
537
|
+
break
|
|
538
|
+
continue
|
|
539
|
+
# defstruct creates a struct - name comes from enclosing module
|
|
540
|
+
elif func_name == "defstruct":
|
|
541
|
+
# defstruct doesn't take a name; use current_class_id to find the module name
|
|
542
|
+
struct_name = None
|
|
543
|
+
if current_class_id is not None and current_class_id < len(classes):
|
|
544
|
+
struct_name = classes[current_class_id].get("name")
|
|
545
|
+
if not struct_name:
|
|
546
|
+
struct_name = "unknown"
|
|
547
|
+
structs.append({
|
|
548
|
+
"type": "struct",
|
|
549
|
+
"name": struct_name,
|
|
550
|
+
"location": get_node_location(node),
|
|
551
|
+
})
|
|
552
|
+
continue
|
|
553
|
+
# defprotocol creates an interface-like construct
|
|
554
|
+
elif func_name == "defprotocol":
|
|
555
|
+
proto_name = None
|
|
556
|
+
for child in node.children:
|
|
557
|
+
if child.type == "arguments":
|
|
558
|
+
for arg in child.children:
|
|
559
|
+
if arg.type == "alias":
|
|
560
|
+
proto_name = extract_node_text(arg, source_code)
|
|
561
|
+
break
|
|
562
|
+
break
|
|
563
|
+
if proto_name:
|
|
564
|
+
interfaces.append({
|
|
565
|
+
"type": "interface",
|
|
566
|
+
"name": proto_name,
|
|
567
|
+
"location": get_node_location(node),
|
|
568
|
+
})
|
|
569
|
+
continue
|
|
570
|
+
# defimpl creates an implementation
|
|
571
|
+
elif func_name == "defimpl":
|
|
572
|
+
impl_name = None
|
|
573
|
+
for child in node.children:
|
|
574
|
+
if child.type == "arguments":
|
|
575
|
+
for arg in child.children:
|
|
576
|
+
if arg.type == "alias":
|
|
577
|
+
impl_name = extract_node_text(arg, source_code)
|
|
578
|
+
break
|
|
579
|
+
break
|
|
580
|
+
if impl_name:
|
|
581
|
+
class_id = len(classes)
|
|
582
|
+
classes.append({
|
|
583
|
+
"type": "class",
|
|
584
|
+
"name": f"{impl_name}Impl",
|
|
585
|
+
"location": get_node_location(node),
|
|
586
|
+
"base_classes": [impl_name],
|
|
587
|
+
"docstring": None,
|
|
588
|
+
"methods": [],
|
|
589
|
+
"nested_classes": [],
|
|
590
|
+
"variables": [],
|
|
591
|
+
"_temp_id": class_id,
|
|
592
|
+
})
|
|
593
|
+
for child in node.children:
|
|
594
|
+
if child.type == "do_block":
|
|
595
|
+
for stmt in child.children:
|
|
596
|
+
stack.append((stmt, False, class_id, current_namespace))
|
|
597
|
+
break
|
|
598
|
+
continue
|
|
599
|
+
|
|
600
|
+
# Macro definitions: C preproc_def/preproc_function_def, Rust macro_definition
|
|
601
|
+
if node.type in ("preproc_def", "preproc_function_def", "macro_definition"):
|
|
602
|
+
macro_info = extract_macro_info(node, source_code, language)
|
|
603
|
+
if macro_info:
|
|
604
|
+
functions.append(macro_info)
|
|
605
|
+
continue
|
|
606
|
+
|
|
607
|
+
# Go: type_declaration covers structs, interfaces, and type aliases
|
|
608
|
+
# Dispatch via extract_go_type_kind before generic branches to avoid conflicts
|
|
609
|
+
if language == "go" and node.type == "type_declaration":
|
|
610
|
+
kind = extract_go_type_kind(node, source_code)
|
|
611
|
+
if kind == "struct":
|
|
612
|
+
struct_info = extract_go_struct_info(node, source_code)
|
|
613
|
+
if struct_info:
|
|
614
|
+
structs.append(struct_info)
|
|
615
|
+
elif kind == "interface":
|
|
616
|
+
iface_info = extract_go_interface_info(node, source_code)
|
|
617
|
+
if iface_info:
|
|
618
|
+
interfaces.append(iface_info)
|
|
619
|
+
else:
|
|
620
|
+
alias_info = extract_type_alias_info(node, source_code, language)
|
|
621
|
+
if alias_info:
|
|
622
|
+
type_aliases.append(alias_info)
|
|
623
|
+
continue
|
|
624
|
+
|
|
625
|
+
if struct_type and node.type in struct_type:
|
|
626
|
+
struct_info = extract_struct_info(node, source_code, language)
|
|
627
|
+
if struct_info:
|
|
628
|
+
structs.append(struct_info)
|
|
629
|
+
# C# and C++ structs have methods and properties like classes — process their bodies
|
|
630
|
+
if language in ("csharp", "cpp"):
|
|
631
|
+
struct_id = len(classes)
|
|
632
|
+
struct_info['_temp_id'] = struct_id
|
|
633
|
+
# Also add to classes so methods can reference it as parent
|
|
634
|
+
classes.append({
|
|
635
|
+
"type": "class",
|
|
636
|
+
"name": struct_info["name"],
|
|
637
|
+
"location": struct_info["location"],
|
|
638
|
+
"base_classes": [],
|
|
639
|
+
"docstring": None,
|
|
640
|
+
"methods": [],
|
|
641
|
+
"nested_classes": [],
|
|
642
|
+
"variables": [],
|
|
643
|
+
"_temp_id": struct_id,
|
|
644
|
+
"namespace": current_namespace,
|
|
645
|
+
})
|
|
646
|
+
_process_class_body(node, source_code, language, struct_id,
|
|
647
|
+
functions, classes, variables, dependencies)
|
|
648
|
+
continue
|
|
649
|
+
|
|
650
|
+
if interface_type and node.type in interface_type:
|
|
651
|
+
iface_info = extract_interface_info(node, source_code, language)
|
|
652
|
+
if iface_info:
|
|
653
|
+
interfaces.append(iface_info)
|
|
654
|
+
# C# interfaces have method declarations — process their bodies
|
|
655
|
+
if language == "csharp":
|
|
656
|
+
iface_id = len(classes)
|
|
657
|
+
iface_info['_temp_id'] = iface_id
|
|
658
|
+
# Also add to classes so methods can reference it as parent
|
|
659
|
+
classes.append({
|
|
660
|
+
"type": "class",
|
|
661
|
+
"name": iface_info["name"],
|
|
662
|
+
"location": iface_info["location"],
|
|
663
|
+
"base_classes": [],
|
|
664
|
+
"docstring": None,
|
|
665
|
+
"methods": [],
|
|
666
|
+
"nested_classes": [],
|
|
667
|
+
"variables": [],
|
|
668
|
+
"_temp_id": iface_id,
|
|
669
|
+
"namespace": current_namespace,
|
|
670
|
+
})
|
|
671
|
+
_process_class_body(node, source_code, language, iface_id,
|
|
672
|
+
functions, classes, variables, dependencies)
|
|
673
|
+
continue
|
|
674
|
+
|
|
675
|
+
if enum_type and node.type in enum_type:
|
|
676
|
+
enum_info = extract_enum_info(node, source_code, language)
|
|
677
|
+
if enum_info:
|
|
678
|
+
enums.append(enum_info)
|
|
679
|
+
continue
|
|
680
|
+
|
|
681
|
+
if type_alias_type and node.type in type_alias_type:
|
|
682
|
+
alias_info = extract_type_alias_info(node, source_code, language)
|
|
683
|
+
if alias_info:
|
|
684
|
+
type_aliases.append(alias_info)
|
|
685
|
+
continue
|
|
686
|
+
|
|
687
|
+
# Push children with same state (reverse order for correct traversal)
|
|
688
|
+
children = list(node.children)
|
|
689
|
+
for child in reversed(children):
|
|
690
|
+
stack.append((child, in_function, current_class_id, current_namespace))
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
def _process_class_body(class_node, source_code, language, class_id,
|
|
694
|
+
functions, classes, variables, dependencies):
|
|
695
|
+
"""Process class body children to extract methods, nested classes, and attributes."""
|
|
696
|
+
node_types = get_node_types(language)
|
|
697
|
+
function_type = node_types.get("function")
|
|
698
|
+
method_type = node_types.get("method")
|
|
699
|
+
class_type = node_types.get("class")
|
|
700
|
+
assignment_type = node_types.get("assignment")
|
|
701
|
+
public_field_type = node_types.get("public_field")
|
|
702
|
+
|
|
703
|
+
# Find the class body
|
|
704
|
+
class_body = None
|
|
705
|
+
for child in class_node.children:
|
|
706
|
+
if child.type in ["class_body", "declaration_list", "block", "field_declaration_list", "class_body"]:
|
|
707
|
+
class_body = child
|
|
708
|
+
break
|
|
709
|
+
|
|
710
|
+
if not class_body:
|
|
711
|
+
return
|
|
712
|
+
|
|
713
|
+
for child in class_body.children:
|
|
714
|
+
if child.type in ["{", "}"]:
|
|
715
|
+
continue
|
|
716
|
+
|
|
717
|
+
if (function_type and child.type in function_type) or \
|
|
718
|
+
(method_type and child.type in method_type):
|
|
719
|
+
func_info = extract_function_info(child, source_code, language, class_type)
|
|
720
|
+
if func_info:
|
|
721
|
+
func_info['parent_class_id'] = class_id
|
|
722
|
+
func_index = len(functions)
|
|
723
|
+
functions.append(func_info)
|
|
724
|
+
# Dart: method_signature and function_body are siblings in class_body
|
|
725
|
+
if language == "dart":
|
|
726
|
+
body = None
|
|
727
|
+
found_self = False
|
|
728
|
+
for sibling in class_body.children:
|
|
729
|
+
if sibling is child:
|
|
730
|
+
found_self = True
|
|
731
|
+
continue
|
|
732
|
+
if found_self and sibling.type == "function_body":
|
|
733
|
+
body = sibling
|
|
734
|
+
break
|
|
735
|
+
if body:
|
|
736
|
+
_extract_deps(child, source_code, language, dependencies, func_index)
|
|
737
|
+
_extract_deps(body, source_code, language, dependencies, func_index)
|
|
738
|
+
else:
|
|
739
|
+
_extract_deps(child, source_code, language, dependencies, func_index)
|
|
740
|
+
else:
|
|
741
|
+
_extract_deps(child, source_code, language, dependencies, func_index)
|
|
742
|
+
elif class_type and child.type in class_type:
|
|
743
|
+
nested_class_info = extract_class_info(child, source_code, language)
|
|
744
|
+
if nested_class_info:
|
|
745
|
+
nested_id = len(classes)
|
|
746
|
+
nested_class_info['_temp_id'] = nested_id
|
|
747
|
+
classes.append(nested_class_info)
|
|
748
|
+
_process_class_body(child, source_code, language, nested_id,
|
|
749
|
+
functions, classes, variables, dependencies)
|
|
750
|
+
elif assignment_type and child.type in assignment_type:
|
|
751
|
+
var_info = extract_variable_info(child, source_code, language)
|
|
752
|
+
if var_info and var_info["type"] == "attribute":
|
|
753
|
+
var_info['parent_class_id'] = class_id
|
|
754
|
+
variables.append(var_info)
|
|
755
|
+
elif public_field_type and child.type in public_field_type:
|
|
756
|
+
name_node = child.child_by_field_name("name")
|
|
757
|
+
if not name_node:
|
|
758
|
+
name_node = child.child_by_field_name("property_identifier")
|
|
759
|
+
name = extract_node_text(name_node, source_code) if name_node else "unknown"
|
|
760
|
+
type_node = child.child_by_field_name("type")
|
|
761
|
+
type_annotation = extract_node_text(type_node, source_code) if type_node else None
|
|
762
|
+
variables.append({
|
|
763
|
+
"type": "variable",
|
|
764
|
+
"name": name,
|
|
765
|
+
"location": get_node_location(child),
|
|
766
|
+
"field_type": type_annotation,
|
|
767
|
+
"parent_class_id": class_id,
|
|
768
|
+
})
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
def _extract_deps(node, source_code, language, dependencies, func_index=None):
|
|
772
|
+
"""Extract dependencies (function calls, class refs, variable refs) from a node.
|
|
773
|
+
|
|
774
|
+
Args:
|
|
775
|
+
func_index: Index of the containing function in the functions list,
|
|
776
|
+
used to later resolve source_function_id.
|
|
777
|
+
"""
|
|
778
|
+
func_calls = extract_function_calls(node, source_code, language)
|
|
779
|
+
class_refs = extract_class_references(node, source_code, language)
|
|
780
|
+
var_refs = extract_variable_references(node, source_code, language)
|
|
781
|
+
for call in func_calls:
|
|
782
|
+
dep = {
|
|
783
|
+
'type': call.get('dependency_type', 'function_call'),
|
|
784
|
+
'name': call['name'],
|
|
785
|
+
'location': call.get('location'),
|
|
786
|
+
}
|
|
787
|
+
if func_index is not None:
|
|
788
|
+
dep['_func_index'] = func_index
|
|
789
|
+
dependencies.append(dep)
|
|
790
|
+
for ref in class_refs:
|
|
791
|
+
dep = {
|
|
792
|
+
'type': 'class_reference',
|
|
793
|
+
'name': ref['name'],
|
|
794
|
+
'location': ref.get('location'),
|
|
795
|
+
}
|
|
796
|
+
if func_index is not None:
|
|
797
|
+
dep['_func_index'] = func_index
|
|
798
|
+
dependencies.append(dep)
|
|
799
|
+
for ref in var_refs:
|
|
800
|
+
dep = {
|
|
801
|
+
'type': 'variable_reference',
|
|
802
|
+
'name': ref['name'],
|
|
803
|
+
'location': ref.get('location'),
|
|
804
|
+
}
|
|
805
|
+
if func_index is not None:
|
|
806
|
+
dep['_func_index'] = func_index
|
|
807
|
+
dependencies.append(dep)
|
|
808
|
+
|
|
809
|
+
|
|
810
|
+
def _parse_html_file(file_path, root_dir, source_bytes, content_hash, file_mtime, config=None):
|
|
811
|
+
"""Parse an HTML file using the frontend HTML extractor.
|
|
812
|
+
|
|
813
|
+
Returns a dict compatible with the standard parse_file output,
|
|
814
|
+
with additional frontend-specific keys.
|
|
815
|
+
"""
|
|
816
|
+
from indexing.frontend.html_extractor import extract_html_semantics
|
|
817
|
+
|
|
818
|
+
try:
|
|
819
|
+
frontend_data = extract_html_semantics(source_bytes, config=config)
|
|
820
|
+
except Exception as e:
|
|
821
|
+
return {
|
|
822
|
+
'file_path': str(file_path),
|
|
823
|
+
'language': 'html',
|
|
824
|
+
'content_hash': content_hash,
|
|
825
|
+
'file_mtime': file_mtime,
|
|
826
|
+
'error': f'HTML extraction failed: {e}',
|
|
827
|
+
'imports': [],
|
|
828
|
+
'functions': [],
|
|
829
|
+
'classes': [],
|
|
830
|
+
'variables': [],
|
|
831
|
+
'type_aliases': [],
|
|
832
|
+
'structs': [],
|
|
833
|
+
'interfaces': [],
|
|
834
|
+
'enums': [],
|
|
835
|
+
'namespaces': [],
|
|
836
|
+
'dependencies': [],
|
|
837
|
+
'markup_elements': [],
|
|
838
|
+
'frontend_events': [],
|
|
839
|
+
'style_selectors': [],
|
|
840
|
+
'style_custom_properties': [],
|
|
841
|
+
'style_custom_property_usages': [],
|
|
842
|
+
'style_imports': [],
|
|
843
|
+
'style_selector_matches': [],
|
|
844
|
+
'frontend_diagnostics': [{
|
|
845
|
+
'diagnostic_type': 'extraction_error',
|
|
846
|
+
'severity': 'fatal',
|
|
847
|
+
'message': str(e),
|
|
848
|
+
}],
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
# Extract script src as imports for dependency tracking
|
|
852
|
+
imports = []
|
|
853
|
+
for script in frontend_data.get("inline_scripts", []):
|
|
854
|
+
if script["type"] == "external":
|
|
855
|
+
imports.append({
|
|
856
|
+
'name': script['src'],
|
|
857
|
+
'location': script.get('location'),
|
|
858
|
+
'is_external': True,
|
|
859
|
+
})
|
|
860
|
+
|
|
861
|
+
# Extract stylesheet links as imports too
|
|
862
|
+
for style_import in frontend_data.get("style_imports", []):
|
|
863
|
+
imports.append({
|
|
864
|
+
'name': style_import['import_path'],
|
|
865
|
+
'location': style_import.get('source_range'),
|
|
866
|
+
'is_external': style_import.get('is_external', False),
|
|
867
|
+
})
|
|
868
|
+
|
|
869
|
+
# Parse inline scripts as JavaScript and merge executable symbols
|
|
870
|
+
functions = []
|
|
871
|
+
classes = []
|
|
872
|
+
variables = []
|
|
873
|
+
dependencies = []
|
|
874
|
+
for script in frontend_data.get("inline_scripts", []):
|
|
875
|
+
if script["type"] != "inline":
|
|
876
|
+
continue
|
|
877
|
+
content = script.get("content", "")
|
|
878
|
+
if not content.strip():
|
|
879
|
+
continue
|
|
880
|
+
script_loc = script.get("location", {})
|
|
881
|
+
script_start_line = script_loc.get("start_line", 0)
|
|
882
|
+
script_start_col = script_loc.get("start_col", 0)
|
|
883
|
+
js_result = _parse_inline_js(content, str(file_path), script_start_line, script_start_col)
|
|
884
|
+
functions.extend(js_result.get("functions", []))
|
|
885
|
+
classes.extend(js_result.get("classes", []))
|
|
886
|
+
variables.extend(js_result.get("variables", []))
|
|
887
|
+
dependencies.extend(js_result.get("dependencies", []))
|
|
888
|
+
|
|
889
|
+
return {
|
|
890
|
+
'file_path': str(file_path),
|
|
891
|
+
'language': 'html',
|
|
892
|
+
'content_hash': content_hash,
|
|
893
|
+
'file_mtime': file_mtime,
|
|
894
|
+
'imports': imports,
|
|
895
|
+
'functions': functions,
|
|
896
|
+
'classes': classes,
|
|
897
|
+
'variables': variables,
|
|
898
|
+
'type_aliases': [],
|
|
899
|
+
'structs': [],
|
|
900
|
+
'interfaces': [],
|
|
901
|
+
'enums': [],
|
|
902
|
+
'namespaces': [],
|
|
903
|
+
'dependencies': dependencies,
|
|
904
|
+
'markup_elements': frontend_data.get('markup_elements', []),
|
|
905
|
+
'frontend_events': frontend_data.get('frontend_events', []),
|
|
906
|
+
'style_selectors': frontend_data.get('style_selectors', []),
|
|
907
|
+
'style_custom_properties': frontend_data.get('style_custom_properties', []),
|
|
908
|
+
'style_custom_property_usages': frontend_data.get('style_custom_property_usages', []),
|
|
909
|
+
'style_imports': frontend_data.get('style_imports', []),
|
|
910
|
+
'style_selector_matches': frontend_data.get('style_selector_matches', []),
|
|
911
|
+
'frontend_diagnostics': frontend_data.get('frontend_diagnostics', []),
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
def _parse_inline_js(content, file_path, offset_line, offset_col):
|
|
916
|
+
"""Parse inline JavaScript content and return extracted symbols.
|
|
917
|
+
|
|
918
|
+
Source locations are adjusted by the script's offset within the HTML file
|
|
919
|
+
so that they point to the correct positions in the parent HTML file.
|
|
920
|
+
|
|
921
|
+
Args:
|
|
922
|
+
content: JavaScript source text from an inline <script> block.
|
|
923
|
+
file_path: Path of the parent HTML file (for symbol location records).
|
|
924
|
+
offset_line: Starting line of the script content within the HTML file.
|
|
925
|
+
offset_col: Starting column of the script content within the HTML file.
|
|
926
|
+
|
|
927
|
+
Returns:
|
|
928
|
+
dict with functions, classes, variables, dependencies lists.
|
|
929
|
+
"""
|
|
930
|
+
result = {"functions": [], "classes": [], "variables": [], "dependencies": []}
|
|
931
|
+
try:
|
|
932
|
+
source_bytes = content.encode("utf-8")
|
|
933
|
+
parser = _get_parser("javascript")
|
|
934
|
+
if parser is None:
|
|
935
|
+
return result
|
|
936
|
+
tree = parser.parse(source_bytes)
|
|
937
|
+
root_node = tree.root_node
|
|
938
|
+
source_code = content
|
|
939
|
+
|
|
940
|
+
functions = []
|
|
941
|
+
classes = []
|
|
942
|
+
variables = []
|
|
943
|
+
type_aliases = []
|
|
944
|
+
structs = []
|
|
945
|
+
interfaces = []
|
|
946
|
+
enums = []
|
|
947
|
+
namespaces = []
|
|
948
|
+
dependencies = []
|
|
949
|
+
|
|
950
|
+
_extract_symbols(
|
|
951
|
+
root_node, source_code, "javascript", Path(file_path), Path(file_path).parent,
|
|
952
|
+
functions, classes, variables, type_aliases, structs, interfaces, enums, namespaces, dependencies
|
|
953
|
+
)
|
|
954
|
+
|
|
955
|
+
def _adjust_location(loc):
|
|
956
|
+
if loc is None:
|
|
957
|
+
return None
|
|
958
|
+
adjusted = dict(loc)
|
|
959
|
+
if "start_line" in adjusted:
|
|
960
|
+
adjusted["start_line"] = adjusted["start_line"] + offset_line
|
|
961
|
+
if "end_line" in adjusted:
|
|
962
|
+
adjusted["end_line"] = adjusted["end_line"] + offset_line
|
|
963
|
+
return adjusted
|
|
964
|
+
|
|
965
|
+
for func in functions:
|
|
966
|
+
func["location"] = _adjust_location(func.get("location"))
|
|
967
|
+
result["functions"].append(func)
|
|
968
|
+
|
|
969
|
+
for cls in classes:
|
|
970
|
+
cls["location"] = _adjust_location(cls.get("location"))
|
|
971
|
+
result["classes"].append(cls)
|
|
972
|
+
|
|
973
|
+
for var in variables:
|
|
974
|
+
var["location"] = _adjust_location(var.get("location"))
|
|
975
|
+
result["variables"].append(var)
|
|
976
|
+
|
|
977
|
+
for dep in dependencies:
|
|
978
|
+
dep["location"] = _adjust_location(dep.get("location"))
|
|
979
|
+
result["dependencies"].append(dep)
|
|
980
|
+
|
|
981
|
+
except Exception:
|
|
982
|
+
pass
|
|
983
|
+
|
|
984
|
+
return result
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
def _parse_css_file(file_path, root_dir, source_bytes, content_hash, file_mtime, config=None):
|
|
988
|
+
"""Parse a CSS file using the frontend CSS extractor.
|
|
989
|
+
|
|
990
|
+
Returns a dict compatible with the standard parse_file output,
|
|
991
|
+
with additional frontend-specific keys.
|
|
992
|
+
"""
|
|
993
|
+
from indexing.frontend.css_extractor import extract_css_semantics
|
|
994
|
+
|
|
995
|
+
try:
|
|
996
|
+
frontend_data = extract_css_semantics(source_bytes, config=config, file_path=str(file_path))
|
|
997
|
+
except Exception as e:
|
|
998
|
+
return {
|
|
999
|
+
'file_path': str(file_path),
|
|
1000
|
+
'language': 'css',
|
|
1001
|
+
'content_hash': content_hash,
|
|
1002
|
+
'file_mtime': file_mtime,
|
|
1003
|
+
'error': f'CSS extraction failed: {e}',
|
|
1004
|
+
'imports': [],
|
|
1005
|
+
'functions': [],
|
|
1006
|
+
'classes': [],
|
|
1007
|
+
'variables': [],
|
|
1008
|
+
'type_aliases': [],
|
|
1009
|
+
'structs': [],
|
|
1010
|
+
'interfaces': [],
|
|
1011
|
+
'enums': [],
|
|
1012
|
+
'namespaces': [],
|
|
1013
|
+
'dependencies': [],
|
|
1014
|
+
'style_selectors': [],
|
|
1015
|
+
'style_custom_properties': [],
|
|
1016
|
+
'style_custom_property_usages': [],
|
|
1017
|
+
'style_keyframes': [],
|
|
1018
|
+
'style_imports': [],
|
|
1019
|
+
'frontend_diagnostics': [{
|
|
1020
|
+
'diagnostic_type': 'extraction_error',
|
|
1021
|
+
'severity': 'fatal',
|
|
1022
|
+
'message': str(e),
|
|
1023
|
+
}],
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
# Extract @import as imports for dependency tracking
|
|
1027
|
+
imports = []
|
|
1028
|
+
for imp in frontend_data.get("style_imports", []):
|
|
1029
|
+
imports.append({
|
|
1030
|
+
'name': imp['import_path'],
|
|
1031
|
+
'location': imp.get('source_range'),
|
|
1032
|
+
'is_external': imp.get('is_external', False),
|
|
1033
|
+
})
|
|
1034
|
+
|
|
1035
|
+
return {
|
|
1036
|
+
'file_path': str(file_path),
|
|
1037
|
+
'language': 'css',
|
|
1038
|
+
'content_hash': content_hash,
|
|
1039
|
+
'file_mtime': file_mtime,
|
|
1040
|
+
'imports': imports,
|
|
1041
|
+
'functions': [],
|
|
1042
|
+
'classes': [],
|
|
1043
|
+
'variables': [],
|
|
1044
|
+
'type_aliases': [],
|
|
1045
|
+
'structs': [],
|
|
1046
|
+
'interfaces': [],
|
|
1047
|
+
'enums': [],
|
|
1048
|
+
'namespaces': [],
|
|
1049
|
+
'dependencies': [],
|
|
1050
|
+
'style_selectors': frontend_data.get('style_selectors', []),
|
|
1051
|
+
'style_custom_properties': frontend_data.get('style_custom_properties', []),
|
|
1052
|
+
'style_custom_property_usages': frontend_data.get('style_custom_property_usages', []),
|
|
1053
|
+
'style_keyframes': frontend_data.get('style_keyframes', []),
|
|
1054
|
+
'style_imports': frontend_data.get('style_imports', []),
|
|
1055
|
+
'frontend_diagnostics': frontend_data.get('frontend_diagnostics', []),
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
|
|
1059
|
+
def _extract_jsx_data(source_bytes, language, config=None, tree=None):
|
|
1060
|
+
"""Extract JSX semantics from JS/TSX source bytes.
|
|
1061
|
+
|
|
1062
|
+
Returns a dict with frontend component data, or empty dict on error.
|
|
1063
|
+
If tree is provided (already parsed by the caller), reuses it instead of re-parsing.
|
|
1064
|
+
"""
|
|
1065
|
+
from indexing.frontend.jsx_extractor import extract_jsx_semantics
|
|
1066
|
+
|
|
1067
|
+
try:
|
|
1068
|
+
return extract_jsx_semantics(source_bytes, language=language, config=config, tree=tree)
|
|
1069
|
+
except Exception as e:
|
|
1070
|
+
return {
|
|
1071
|
+
"frontend_components": [],
|
|
1072
|
+
"markup_elements": [],
|
|
1073
|
+
"frontend_events": [],
|
|
1074
|
+
"frontend_bindings": [],
|
|
1075
|
+
"render_relationships": [],
|
|
1076
|
+
"style_selector_matches": [],
|
|
1077
|
+
"frontend_diagnostics": [{
|
|
1078
|
+
"diagnostic_type": "jsx_extraction_error",
|
|
1079
|
+
"severity": "recoverable",
|
|
1080
|
+
"message": str(e),
|
|
1081
|
+
}],
|
|
1082
|
+
}
|