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/node_utils.py
ADDED
|
@@ -0,0 +1,1112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions for working with tree-sitter nodes.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_node_location(node):
|
|
8
|
+
"""Get start and end line/column for a node."""
|
|
9
|
+
start_line, start_col = node.start_point
|
|
10
|
+
end_line, end_col = node.end_point
|
|
11
|
+
return {
|
|
12
|
+
"start_line": start_line + 1,
|
|
13
|
+
"start_column": start_col,
|
|
14
|
+
"end_line": end_line + 1,
|
|
15
|
+
"end_column": end_col,
|
|
16
|
+
"start_byte": node.start_byte,
|
|
17
|
+
"end_byte": node.end_byte
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def extract_node_text(node, source_code):
|
|
22
|
+
"""Extract the text content of a node from source code.
|
|
23
|
+
|
|
24
|
+
Accepts either a str (which will be encoded to UTF-8 each call) or bytes
|
|
25
|
+
(which is used directly, avoiding repeated whole-source encoding).
|
|
26
|
+
"""
|
|
27
|
+
if isinstance(source_code, bytes):
|
|
28
|
+
return source_code[node.start_byte:node.end_byte].decode('utf-8')
|
|
29
|
+
source_bytes = source_code.encode('utf-8')
|
|
30
|
+
return source_bytes[node.start_byte:node.end_byte].decode('utf-8')
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def extract_field_text(node, field_name, source_code):
|
|
34
|
+
"""Extract text from a specific field of a node."""
|
|
35
|
+
field_node = node.child_by_field_name(field_name)
|
|
36
|
+
if field_node:
|
|
37
|
+
return extract_node_text(field_node, source_code)
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def extract_name(node, source_code, language=None):
|
|
42
|
+
"""Extract the name from a node (function, class, variable, etc.)."""
|
|
43
|
+
# Try the standard "name" field first
|
|
44
|
+
name = extract_field_text(node, "name", source_code)
|
|
45
|
+
if name:
|
|
46
|
+
return name
|
|
47
|
+
|
|
48
|
+
# TypeScript/JavaScript specific: look for identifier children
|
|
49
|
+
# For function declarations, the name is typically the first identifier
|
|
50
|
+
# We need to be careful not to pick up identifiers from the function body
|
|
51
|
+
if node.type == "function_declaration":
|
|
52
|
+
# The name should be the first identifier child (before parameters)
|
|
53
|
+
for child in node.children:
|
|
54
|
+
# Kotlin uses simple_identifier
|
|
55
|
+
if child.type in ["identifier", "simple_identifier"]:
|
|
56
|
+
return extract_node_text(child, source_code)
|
|
57
|
+
# Stop if we've gone past the name (parameters start)
|
|
58
|
+
if child.type in ["formal_parameters", "function_value_parameters"]:
|
|
59
|
+
break
|
|
60
|
+
elif node.type == "method_declaration":
|
|
61
|
+
if language == "go":
|
|
62
|
+
# Go method: func (recv *Type) Name() - name is field_identifier
|
|
63
|
+
for child in node.children:
|
|
64
|
+
if child.type == "field_identifier":
|
|
65
|
+
return extract_node_text(child, source_code)
|
|
66
|
+
if child.type == "identifier":
|
|
67
|
+
return extract_node_text(child, source_code)
|
|
68
|
+
# C# method declaration - look for identifier
|
|
69
|
+
for child in node.children:
|
|
70
|
+
if child.type == "identifier":
|
|
71
|
+
return extract_node_text(child, source_code)
|
|
72
|
+
# Stop if we've gone past the name (parameters start)
|
|
73
|
+
if child.type == "parameter_list":
|
|
74
|
+
break
|
|
75
|
+
elif node.type == "class_declaration":
|
|
76
|
+
# For classes, look for type_identifier
|
|
77
|
+
for child in node.children:
|
|
78
|
+
if child.type == "type_identifier":
|
|
79
|
+
return extract_node_text(child, source_code)
|
|
80
|
+
if child.type == "class_heritage":
|
|
81
|
+
break
|
|
82
|
+
elif node.type in ("function_definition", "declaration") and language in ("cpp", "c"):
|
|
83
|
+
# C++ function_definition: name is inside function_declarator or declarator
|
|
84
|
+
for child in node.children:
|
|
85
|
+
if child.type in ("function_declarator", "declarator", "reference_declarator", "pointer_declarator"):
|
|
86
|
+
# Find the identifier or field_identifier inside the declarator
|
|
87
|
+
stack = [child]
|
|
88
|
+
while stack:
|
|
89
|
+
n = stack.pop()
|
|
90
|
+
if n.type in ("identifier", "field_identifier"):
|
|
91
|
+
return extract_node_text(n, source_code)
|
|
92
|
+
stack.extend(reversed(list(n.children)))
|
|
93
|
+
elif node.type == "class_specifier" and language == "cpp":
|
|
94
|
+
# C++ class_specifier: name is a type_identifier child
|
|
95
|
+
for child in node.children:
|
|
96
|
+
if child.type == "type_identifier":
|
|
97
|
+
return extract_node_text(child, source_code)
|
|
98
|
+
elif node.type == "type_declaration" and language == "go":
|
|
99
|
+
return extract_go_type_name(node, source_code)
|
|
100
|
+
elif node.type == "struct_specifier" and language in ("cpp", "c"):
|
|
101
|
+
# C/C++ struct_specifier: name is a type_identifier child
|
|
102
|
+
for child in node.children:
|
|
103
|
+
if child.type == "type_identifier":
|
|
104
|
+
return extract_node_text(child, source_code)
|
|
105
|
+
elif node.type == "enum_specifier" and language in ("cpp", "c"):
|
|
106
|
+
# C/C++ enum_specifier: name is a type_identifier child
|
|
107
|
+
for child in node.children:
|
|
108
|
+
if child.type == "type_identifier":
|
|
109
|
+
return extract_node_text(child, source_code)
|
|
110
|
+
elif node.type == "method_signature" and language == "dart":
|
|
111
|
+
# Dart method_signature wraps function_signature/getter_signature/etc.
|
|
112
|
+
# The name is inside the inner signature
|
|
113
|
+
for child in node.children:
|
|
114
|
+
if child.type in ("function_signature", "getter_signature", "setter_signature", "constructor_signature"):
|
|
115
|
+
for grandchild in child.children:
|
|
116
|
+
if grandchild.type == "identifier":
|
|
117
|
+
return extract_node_text(grandchild, source_code)
|
|
118
|
+
else:
|
|
119
|
+
# For other node types, look for the first identifier
|
|
120
|
+
# Kotlin uses simple_identifier
|
|
121
|
+
for child in node.children:
|
|
122
|
+
if child.type in ["identifier", "property_identifier", "type_identifier", "simple_identifier"]:
|
|
123
|
+
return extract_node_text(child, source_code)
|
|
124
|
+
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def extract_parameters(node, source_code, language):
|
|
129
|
+
"""Extract parameter names from a function node."""
|
|
130
|
+
params = []
|
|
131
|
+
params_node = node.child_by_field_name("parameters")
|
|
132
|
+
|
|
133
|
+
# C# uses "parameter_list" as a child, not a field
|
|
134
|
+
if not params_node and language == "csharp":
|
|
135
|
+
for child in node.children:
|
|
136
|
+
if child.type == "parameter_list":
|
|
137
|
+
params_node = child
|
|
138
|
+
break
|
|
139
|
+
|
|
140
|
+
# Dart uses "formal_parameter_list" as a child
|
|
141
|
+
if not params_node and language == "dart":
|
|
142
|
+
for child in node.children:
|
|
143
|
+
if child.type == "formal_parameter_list":
|
|
144
|
+
params_node = child
|
|
145
|
+
break
|
|
146
|
+
# For method_signature, look inside the inner signature
|
|
147
|
+
if not params_node:
|
|
148
|
+
for child in node.children:
|
|
149
|
+
if child.type in ("function_signature", "getter_signature", "setter_signature", "constructor_signature"):
|
|
150
|
+
for grandchild in child.children:
|
|
151
|
+
if grandchild.type == "formal_parameter_list":
|
|
152
|
+
params_node = grandchild
|
|
153
|
+
break
|
|
154
|
+
break
|
|
155
|
+
|
|
156
|
+
if not params_node:
|
|
157
|
+
return params
|
|
158
|
+
|
|
159
|
+
# Language-specific parameter extraction
|
|
160
|
+
if language == "python":
|
|
161
|
+
for child in params_node.children:
|
|
162
|
+
if child.type == "identifier":
|
|
163
|
+
params.append(extract_node_text(child, source_code))
|
|
164
|
+
elif language in ["go", "rust", "cpp", "php"]:
|
|
165
|
+
for child in params_node.children:
|
|
166
|
+
if child.type in ["identifier", "parameter"]:
|
|
167
|
+
params.append(extract_node_text(child, source_code))
|
|
168
|
+
elif language in ["javascript", "typescript", "tsx", "csharp"]:
|
|
169
|
+
for child in params_node.children:
|
|
170
|
+
if child.type == "identifier":
|
|
171
|
+
params.append(extract_node_text(child, source_code))
|
|
172
|
+
elif language in ["java", "kotlin", "dart"]:
|
|
173
|
+
for child in params_node.children:
|
|
174
|
+
if child.type in ["identifier", "simple_identifier", "formal_parameter"]:
|
|
175
|
+
if child.type == "formal_parameter":
|
|
176
|
+
# Extract identifier from inside formal_parameter
|
|
177
|
+
for grandchild in child.children:
|
|
178
|
+
if grandchild.type in ["identifier", "simple_identifier"]:
|
|
179
|
+
params.append(extract_node_text(grandchild, source_code))
|
|
180
|
+
break
|
|
181
|
+
else:
|
|
182
|
+
params.append(extract_node_text(child, source_code))
|
|
183
|
+
elif language == "zig":
|
|
184
|
+
for child in params_node.children:
|
|
185
|
+
if child.type in ["identifier", "parameter"]:
|
|
186
|
+
params.append(extract_node_text(child, source_code))
|
|
187
|
+
elif language == "elixir":
|
|
188
|
+
for child in params_node.children:
|
|
189
|
+
if child.type in ["identifier", "atom"]:
|
|
190
|
+
params.append(extract_node_text(child, source_code))
|
|
191
|
+
|
|
192
|
+
return params
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def extract_return_type(node, source_code):
|
|
196
|
+
"""Extract return type from a function node."""
|
|
197
|
+
return extract_field_text(node, "return_type", source_code)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def is_method(node, language, class_node_type):
|
|
201
|
+
"""Check if a function node is a method (inside a class)."""
|
|
202
|
+
if not node.parent:
|
|
203
|
+
return False
|
|
204
|
+
if not class_node_type:
|
|
205
|
+
return False
|
|
206
|
+
if isinstance(class_node_type, list):
|
|
207
|
+
return node.parent.type in class_node_type
|
|
208
|
+
return node.parent.type == class_node_type
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def extract_base_classes(node, source_code, language):
|
|
212
|
+
"""Extract base class names from a class node."""
|
|
213
|
+
base_classes = []
|
|
214
|
+
|
|
215
|
+
if language == "python":
|
|
216
|
+
arg_list = node.child_by_field_name("superclasses")
|
|
217
|
+
if arg_list:
|
|
218
|
+
for child in arg_list.children:
|
|
219
|
+
if child.type in ["identifier", "attribute"]:
|
|
220
|
+
base_classes.append(extract_node_text(child, source_code))
|
|
221
|
+
elif language in ["csharp", "cpp"]:
|
|
222
|
+
# C# base_list has no field name, find by type
|
|
223
|
+
base_list = None
|
|
224
|
+
for child in node.children:
|
|
225
|
+
if child.type == "base_list":
|
|
226
|
+
base_list = child
|
|
227
|
+
break
|
|
228
|
+
if not base_list:
|
|
229
|
+
base_list = node.child_by_field_name("bases")
|
|
230
|
+
if base_list:
|
|
231
|
+
for child in base_list.children:
|
|
232
|
+
if child.type in ["identifier", "type_identifier"]:
|
|
233
|
+
base_classes.append(extract_node_text(child, source_code))
|
|
234
|
+
elif language in ["javascript", "typescript", "tsx"]:
|
|
235
|
+
# TypeScript/JavaScript uses class_heritage as a child, not a field
|
|
236
|
+
heritage = None
|
|
237
|
+
for child in node.children:
|
|
238
|
+
if child.type == "class_heritage":
|
|
239
|
+
heritage = child
|
|
240
|
+
break
|
|
241
|
+
|
|
242
|
+
if heritage:
|
|
243
|
+
# Look for extends_clause or direct identifiers
|
|
244
|
+
for child in heritage.children:
|
|
245
|
+
if child.type == "extends_clause":
|
|
246
|
+
# Find the identifier inside extends_clause
|
|
247
|
+
for grandchild in child.children:
|
|
248
|
+
if grandchild.type in ["identifier", "type_identifier"]:
|
|
249
|
+
base_classes.append(extract_node_text(grandchild, source_code))
|
|
250
|
+
elif child.type in ["identifier", "type_identifier"]:
|
|
251
|
+
base_classes.append(extract_node_text(child, source_code))
|
|
252
|
+
elif language == "php":
|
|
253
|
+
# PHP uses "extends" keyword
|
|
254
|
+
base_class_node = node.child_by_field_name("base")
|
|
255
|
+
if base_class_node:
|
|
256
|
+
for child in base_class_node.children:
|
|
257
|
+
if child.type in ["identifier", "name"]:
|
|
258
|
+
base_classes.append(extract_node_text(child, source_code))
|
|
259
|
+
elif language == "java":
|
|
260
|
+
# Java: extends superclass, implements interfaces
|
|
261
|
+
superclass = node.child_by_field_name("superclass")
|
|
262
|
+
if superclass:
|
|
263
|
+
for child in superclass.children:
|
|
264
|
+
if child.type in ["type_identifier", "identifier"]:
|
|
265
|
+
base_classes.append(extract_node_text(child, source_code))
|
|
266
|
+
interfaces = node.child_by_field_name("super_interfaces")
|
|
267
|
+
if interfaces:
|
|
268
|
+
for child in interfaces.children:
|
|
269
|
+
if child.type in ["type_identifier", "identifier"]:
|
|
270
|
+
base_classes.append(extract_node_text(child, source_code))
|
|
271
|
+
elif language == "kotlin":
|
|
272
|
+
# Kotlin: superclass and super_type_list
|
|
273
|
+
for child in node.children:
|
|
274
|
+
if child.type in ["superclass", "delegation_specifier"]:
|
|
275
|
+
for grandchild in child.children:
|
|
276
|
+
if grandchild.type in ["type_identifier", "identifier", "user_type", "constructor_invocation"]:
|
|
277
|
+
base_classes.append(extract_node_text(grandchild, source_code))
|
|
278
|
+
elif language == "dart":
|
|
279
|
+
# Dart: extends and with (mixins), implements
|
|
280
|
+
for child in node.children:
|
|
281
|
+
if child.type in ["superclass", "with_clause", "implements_clause"]:
|
|
282
|
+
for grandchild in child.children:
|
|
283
|
+
if grandchild.type in ["type_identifier", "identifier", "mixin_identifier"]:
|
|
284
|
+
base_classes.append(extract_node_text(grandchild, source_code))
|
|
285
|
+
elif language == "rust":
|
|
286
|
+
# Rust traits can have supertraits
|
|
287
|
+
bounds = node.child_by_field_name("bounds")
|
|
288
|
+
if bounds:
|
|
289
|
+
for child in bounds.children:
|
|
290
|
+
if child.type in ["type_identifier", "identifier", "scoped_identifier"]:
|
|
291
|
+
base_classes.append(extract_node_text(child, source_code))
|
|
292
|
+
|
|
293
|
+
return base_classes
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def extract_variable_name(node, source_code, language):
|
|
297
|
+
"""Extract variable name from an assignment node."""
|
|
298
|
+
name = None
|
|
299
|
+
|
|
300
|
+
if language == "python":
|
|
301
|
+
left_node = node.child_by_field_name("left")
|
|
302
|
+
if left_node:
|
|
303
|
+
if left_node.type == "identifier":
|
|
304
|
+
name = extract_node_text(left_node, source_code)
|
|
305
|
+
elif left_node.type == "attribute":
|
|
306
|
+
return extract_node_text(left_node, source_code), True # Return as attribute
|
|
307
|
+
elif language in ["go", "rust"]:
|
|
308
|
+
name_node = node.child_by_field_name("name")
|
|
309
|
+
if name_node:
|
|
310
|
+
name = extract_node_text(name_node, source_code)
|
|
311
|
+
elif language == "c":
|
|
312
|
+
# C declaration: int x; int x = 5; static char *msg; int arr[1024];
|
|
313
|
+
# Look for any declarator child (init_declarator, array_declarator, etc.)
|
|
314
|
+
# and extract the identifier from within it.
|
|
315
|
+
declarator_types = {"init_declarator", "array_declarator", "pointer_declarator",
|
|
316
|
+
"function_declarator", "parenthesized_declarator"}
|
|
317
|
+
for child in node.children:
|
|
318
|
+
if child.type in declarator_types:
|
|
319
|
+
stack = [child]
|
|
320
|
+
while stack:
|
|
321
|
+
n = stack.pop()
|
|
322
|
+
if n.type == "identifier":
|
|
323
|
+
name = extract_node_text(n, source_code)
|
|
324
|
+
break
|
|
325
|
+
stack.extend(reversed(list(n.children)))
|
|
326
|
+
if name:
|
|
327
|
+
break
|
|
328
|
+
elif language in ["javascript", "typescript", "tsx", "csharp", "cpp", "php"]:
|
|
329
|
+
# Handle lexical_declaration / variable_declaration (const/let/var x = ...)
|
|
330
|
+
if node.type in ("lexical_declaration", "variable_declaration"):
|
|
331
|
+
for child in node.children:
|
|
332
|
+
if child.type == "variable_declarator":
|
|
333
|
+
name_node = child.child_by_field_name("name")
|
|
334
|
+
if name_node:
|
|
335
|
+
name = extract_node_text(name_node, source_code)
|
|
336
|
+
break
|
|
337
|
+
return name, False
|
|
338
|
+
left_node = node.child_by_field_name("left")
|
|
339
|
+
if left_node:
|
|
340
|
+
if left_node.type == "identifier":
|
|
341
|
+
name = extract_node_text(left_node, source_code)
|
|
342
|
+
elif left_node.type == "member_expression":
|
|
343
|
+
return extract_node_text(left_node, source_code), True # Return as attribute
|
|
344
|
+
elif language in ["java", "dart"]:
|
|
345
|
+
left_node = node.child_by_field_name("left")
|
|
346
|
+
if left_node:
|
|
347
|
+
if left_node.type in ["identifier", "simple_identifier"]:
|
|
348
|
+
name = extract_node_text(left_node, source_code)
|
|
349
|
+
elif left_node.type in ["member_expression", "field_access_expression", "navigation_expression"]:
|
|
350
|
+
return extract_node_text(left_node, source_code), True
|
|
351
|
+
elif language == "kotlin":
|
|
352
|
+
# Kotlin assignment: directly_assignable_expression wraps simple_identifier
|
|
353
|
+
for child in node.children:
|
|
354
|
+
if child.type == "directly_assignable_expression":
|
|
355
|
+
for grandchild in child.children:
|
|
356
|
+
if grandchild.type in ["simple_identifier", "identifier"]:
|
|
357
|
+
name = extract_node_text(grandchild, source_code)
|
|
358
|
+
break
|
|
359
|
+
break
|
|
360
|
+
elif child.type in ["simple_identifier", "identifier"]:
|
|
361
|
+
name = extract_node_text(child, source_code)
|
|
362
|
+
break
|
|
363
|
+
elif language == "zig":
|
|
364
|
+
# Zig variable_declaration: const/var identifier = value
|
|
365
|
+
# The identifier is a direct child, not a 'left' field
|
|
366
|
+
for child in node.children:
|
|
367
|
+
if child.type == "identifier":
|
|
368
|
+
name = extract_node_text(child, source_code)
|
|
369
|
+
break
|
|
370
|
+
elif language == "elixir":
|
|
371
|
+
# Elixir: binary_operator with = is an assignment
|
|
372
|
+
# Only treat as assignment if operator is "="
|
|
373
|
+
op_node = node.child_by_field_name("operator")
|
|
374
|
+
if op_node and extract_node_text(op_node, source_code) == "=":
|
|
375
|
+
left_node = node.child_by_field_name("left")
|
|
376
|
+
if left_node and left_node.type == "identifier":
|
|
377
|
+
name = extract_node_text(left_node, source_code)
|
|
378
|
+
|
|
379
|
+
return name, False
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def extract_docstring(node, source_code, language):
|
|
383
|
+
"""Extract docstring from a node if present."""
|
|
384
|
+
if language == "python":
|
|
385
|
+
for child in node.children:
|
|
386
|
+
if child.type == "block":
|
|
387
|
+
for grandchild in child.children:
|
|
388
|
+
if grandchild.type == "expression_statement":
|
|
389
|
+
string_node = None
|
|
390
|
+
for gc in grandchild.children:
|
|
391
|
+
if gc.type == "string":
|
|
392
|
+
string_node = gc
|
|
393
|
+
break
|
|
394
|
+
if string_node:
|
|
395
|
+
docstring = extract_node_text(string_node, source_code)
|
|
396
|
+
if docstring.startswith('"""') or docstring.startswith("'''"):
|
|
397
|
+
return docstring[3:-3]
|
|
398
|
+
elif docstring.startswith('"') or docstring.startswith("'"):
|
|
399
|
+
return docstring[1:-1]
|
|
400
|
+
# Other languages would need comment extraction logic
|
|
401
|
+
return None
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def extract_go_receiver(node, source_code):
|
|
405
|
+
"""Extract receiver information from a Go method declaration."""
|
|
406
|
+
receiver_node = node.child_by_field_name("receiver")
|
|
407
|
+
if receiver_node:
|
|
408
|
+
return extract_node_text(receiver_node, source_code)
|
|
409
|
+
return None
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def extract_go_type_name(node, source_code):
|
|
413
|
+
"""Extract type name from a Go type_declaration node (struct or interface)."""
|
|
414
|
+
# Go type_declaration has a "name" field for the type identifier
|
|
415
|
+
name_node = node.child_by_field_name("name")
|
|
416
|
+
if name_node:
|
|
417
|
+
return extract_node_text(name_node, source_code)
|
|
418
|
+
|
|
419
|
+
# Look inside type_spec for the type_identifier
|
|
420
|
+
for child in node.children:
|
|
421
|
+
if child.type == "type_spec":
|
|
422
|
+
for grandchild in child.children:
|
|
423
|
+
if grandchild.type == "type_identifier":
|
|
424
|
+
return extract_node_text(grandchild, source_code)
|
|
425
|
+
if child.type == "type_identifier":
|
|
426
|
+
return extract_node_text(child, source_code)
|
|
427
|
+
|
|
428
|
+
return None
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def extract_go_type_kind(node, source_code):
|
|
432
|
+
"""Determine if a Go type_declaration is a struct, interface, or type alias."""
|
|
433
|
+
# Look for type_spec child which contains the actual type info
|
|
434
|
+
type_spec = None
|
|
435
|
+
for child in node.children:
|
|
436
|
+
if child.type == "type_spec":
|
|
437
|
+
type_spec = child
|
|
438
|
+
break
|
|
439
|
+
|
|
440
|
+
if not type_spec:
|
|
441
|
+
# Fallback: try field name
|
|
442
|
+
type_spec = node.child_by_field_name("type")
|
|
443
|
+
|
|
444
|
+
if not type_spec:
|
|
445
|
+
return "type_alias"
|
|
446
|
+
|
|
447
|
+
# Check the type body to determine kind
|
|
448
|
+
for child in type_spec.children:
|
|
449
|
+
if child.type == "struct_type":
|
|
450
|
+
return "struct"
|
|
451
|
+
elif child.type == "interface_type":
|
|
452
|
+
return "interface"
|
|
453
|
+
|
|
454
|
+
return "type_alias"
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def count_branches(node, language, source_code=None):
|
|
458
|
+
"""Count the number of conditional branches in a function body (iterative)."""
|
|
459
|
+
branch_count = 0
|
|
460
|
+
|
|
461
|
+
branch_types = {
|
|
462
|
+
'python': ['if_statement', 'for_statement', 'while_statement', 'match_statement', 'try_statement'],
|
|
463
|
+
'go': ['if_statement', 'for_statement', 'for_range_clause', 'switch_statement', 'select_statement'],
|
|
464
|
+
'javascript': ['if_statement', 'for_statement', 'for_in_statement', 'for_of_statement', 'while_statement', 'switch_statement', 'try_statement', 'do_statement'],
|
|
465
|
+
'typescript': ['if_statement', 'for_statement', 'for_in_statement', 'for_of_statement', 'while_statement', 'switch_statement', 'try_statement'],
|
|
466
|
+
'tsx': ['if_statement', 'for_statement', 'for_in_statement', 'for_of_statement', 'while_statement', 'switch_statement', 'try_statement'],
|
|
467
|
+
'csharp': ['if_statement', 'for_statement', 'foreach_statement', 'while_statement', 'switch_statement', 'try_statement'],
|
|
468
|
+
'rust': ['if_expression', 'for_expression', 'while_expression', 'loop_expression', 'match_expression', 'if_let_expression', 'while_let_expression'],
|
|
469
|
+
'cpp': ['if_statement', 'for_statement', 'range_based_for_statement', 'while_statement', 'switch_statement', 'try_statement', 'do_statement'],
|
|
470
|
+
'zig': ['if_statement', 'for_statement', 'while_statement', 'switch_statement'],
|
|
471
|
+
'elixir': ['call'],
|
|
472
|
+
'php': ['if_statement', 'for_statement', 'foreach_statement', 'while_statement', 'switch_statement', 'try_statement', 'match_expression'],
|
|
473
|
+
'dart': ['if_statement', 'for_statement', 'while_statement', 'switch_statement', 'try_statement'],
|
|
474
|
+
'java': ['if_statement', 'for_statement', 'enhanced_for_statement', 'while_statement', 'switch_statement', 'try_statement', 'try_with_resources_statement', 'do_statement'],
|
|
475
|
+
'kotlin': ['if_expression', 'for_statement', 'while_statement', 'do_while_statement', 'when_expression', 'try_expression']
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
lang_branch_types = branch_types.get(language, ['if_statement', 'for_statement', 'while_statement', 'switch_statement'])
|
|
479
|
+
|
|
480
|
+
elixir_branch_keywords = {'if', 'case', 'cond', 'try', 'receive', 'for', 'with', 'unless'}
|
|
481
|
+
|
|
482
|
+
stack = [node]
|
|
483
|
+
while stack:
|
|
484
|
+
n = stack.pop()
|
|
485
|
+
if n.type in lang_branch_types:
|
|
486
|
+
if language == 'elixir' and n.type == 'call':
|
|
487
|
+
first_ident = None
|
|
488
|
+
for child in n.children:
|
|
489
|
+
if child.type == 'identifier':
|
|
490
|
+
first_ident = extract_node_text(child, source_code)
|
|
491
|
+
break
|
|
492
|
+
if first_ident in elixir_branch_keywords:
|
|
493
|
+
branch_count += 1
|
|
494
|
+
else:
|
|
495
|
+
branch_count += 1
|
|
496
|
+
stack.extend(reversed(list(n.children)))
|
|
497
|
+
|
|
498
|
+
return branch_count
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def extract_imports(node, source_code, language, root_dir=None):
|
|
502
|
+
"""Extract import statements from a file (iterative)."""
|
|
503
|
+
imports = []
|
|
504
|
+
|
|
505
|
+
import_types = {
|
|
506
|
+
'python': ['import_statement', 'import_from_statement'],
|
|
507
|
+
'go': ['import_declaration'],
|
|
508
|
+
'javascript': ['import_statement', 'import_declaration'],
|
|
509
|
+
'typescript': ['import_statement', 'import_declaration'],
|
|
510
|
+
'tsx': ['import_statement', 'import_declaration'],
|
|
511
|
+
'csharp': ['using_directive', 'global_using_directive'],
|
|
512
|
+
'rust': ['use_declaration', 'extern_crate_declaration'],
|
|
513
|
+
'cpp': ['include_directive', 'preproc_include'],
|
|
514
|
+
'c': ['include_directive', 'preproc_include'],
|
|
515
|
+
'zig': ['builtin_function'],
|
|
516
|
+
'elixir': ['alias'],
|
|
517
|
+
'php': ['include_expression', 'include_once_expression', 'require_expression', 'require_once_expression', 'namespace_use_declaration'],
|
|
518
|
+
'dart': ['import_or_export'],
|
|
519
|
+
'java': ['import_declaration'],
|
|
520
|
+
'kotlin': ['import_header']
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
lang_import_types = import_types.get(language, ['import_statement'])
|
|
524
|
+
|
|
525
|
+
stack = [node]
|
|
526
|
+
while stack:
|
|
527
|
+
n = stack.pop()
|
|
528
|
+
if n.type in lang_import_types:
|
|
529
|
+
# Zig: only @import builtin_function calls are imports
|
|
530
|
+
if language == "zig" and n.type == "builtin_function":
|
|
531
|
+
text = extract_node_text(n, source_code)
|
|
532
|
+
if not text.startswith("@import"):
|
|
533
|
+
stack.extend(reversed(list(n.children)))
|
|
534
|
+
continue
|
|
535
|
+
# Go: import_declaration may contain import_spec_list with multiple import_spec
|
|
536
|
+
if language == "go" and n.type == "import_declaration":
|
|
537
|
+
specs = []
|
|
538
|
+
for child in n.children:
|
|
539
|
+
if child.type == "import_spec_list":
|
|
540
|
+
for spec in child.children:
|
|
541
|
+
if spec.type == "import_spec":
|
|
542
|
+
specs.append(spec)
|
|
543
|
+
if specs:
|
|
544
|
+
for spec in specs:
|
|
545
|
+
import_text = extract_node_text(spec, source_code)
|
|
546
|
+
is_external = True
|
|
547
|
+
if root_dir:
|
|
548
|
+
is_external = _is_external_import(import_text, language, root_dir)
|
|
549
|
+
imports.append({
|
|
550
|
+
'name': import_text,
|
|
551
|
+
'location': get_node_location(spec),
|
|
552
|
+
'is_external': is_external
|
|
553
|
+
})
|
|
554
|
+
continue
|
|
555
|
+
import_text = extract_node_text(n, source_code)
|
|
556
|
+
is_external = True
|
|
557
|
+
if root_dir:
|
|
558
|
+
is_external = _is_external_import(import_text, language, root_dir)
|
|
559
|
+
imports.append({
|
|
560
|
+
'name': import_text,
|
|
561
|
+
'location': get_node_location(n),
|
|
562
|
+
'is_external': is_external
|
|
563
|
+
})
|
|
564
|
+
stack.extend(reversed(list(n.children)))
|
|
565
|
+
|
|
566
|
+
return imports
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def _is_external_import(import_text, language, root_dir):
|
|
570
|
+
"""Determine if an import is external (not part of the codebase)."""
|
|
571
|
+
from pathlib import Path
|
|
572
|
+
|
|
573
|
+
root_path = Path(root_dir)
|
|
574
|
+
|
|
575
|
+
if language == 'python':
|
|
576
|
+
# Extract the base module name from the import
|
|
577
|
+
# e.g., "import os.path" -> "os", "from myapp.models import User" -> "myapp"
|
|
578
|
+
if import_text.startswith('from '):
|
|
579
|
+
# "from myapp.models import User"
|
|
580
|
+
parts = import_text.split()
|
|
581
|
+
if len(parts) >= 2:
|
|
582
|
+
module_name = parts[1].split('.')[0]
|
|
583
|
+
else:
|
|
584
|
+
return True
|
|
585
|
+
else:
|
|
586
|
+
# "import os" or "import os.path"
|
|
587
|
+
parts = import_text.split()
|
|
588
|
+
if len(parts) >= 2:
|
|
589
|
+
module_name = parts[1].split('.')[0]
|
|
590
|
+
else:
|
|
591
|
+
return True
|
|
592
|
+
|
|
593
|
+
# Check if this module exists in the codebase
|
|
594
|
+
module_path = root_path / module_name
|
|
595
|
+
if module_path.exists() and (module_path.is_dir() or module_path.with_suffix('.py').exists()):
|
|
596
|
+
return False # Internal
|
|
597
|
+
|
|
598
|
+
# Also check for __init__.py
|
|
599
|
+
init_path = module_path / '__init__.py'
|
|
600
|
+
if init_path.exists():
|
|
601
|
+
return False # Internal
|
|
602
|
+
|
|
603
|
+
elif language == 'go':
|
|
604
|
+
# Go imports are usually full paths like "github.com/user/repo/module"
|
|
605
|
+
# Check if it's a relative import or local package
|
|
606
|
+
if import_text.startswith('"'):
|
|
607
|
+
import_path = import_text.strip('"')
|
|
608
|
+
else:
|
|
609
|
+
import_path = import_text
|
|
610
|
+
|
|
611
|
+
# Relative imports are internal
|
|
612
|
+
if import_path.startswith('.'):
|
|
613
|
+
return False
|
|
614
|
+
|
|
615
|
+
# Check if it matches a directory in the codebase
|
|
616
|
+
module_name = import_path.split('/')[-1]
|
|
617
|
+
if (root_path / module_name).exists():
|
|
618
|
+
return False
|
|
619
|
+
|
|
620
|
+
elif language in ['javascript', 'typescript', 'tsx']:
|
|
621
|
+
# Extract the module path from the import statement
|
|
622
|
+
# e.g. import { foo } from "./local"; -> ./local
|
|
623
|
+
import_path = None
|
|
624
|
+
if 'from ' in import_text:
|
|
625
|
+
# import { x } from "path"
|
|
626
|
+
parts = import_text.split('from ')
|
|
627
|
+
if len(parts) >= 2:
|
|
628
|
+
path_part = parts[-1].strip().rstrip(';').strip()
|
|
629
|
+
import_path = path_part.strip('"\'`')
|
|
630
|
+
elif import_text.startswith('import '):
|
|
631
|
+
# import "path" or import "path";
|
|
632
|
+
path_part = import_text[len('import '):].strip().rstrip(';').strip()
|
|
633
|
+
import_path = path_part.strip('"\'`')
|
|
634
|
+
|
|
635
|
+
if import_path:
|
|
636
|
+
if import_path.startswith('./') or import_path.startswith('../'):
|
|
637
|
+
return False
|
|
638
|
+
if not import_path.startswith('.') and not import_path.startswith('@'):
|
|
639
|
+
module_path = root_path / import_path
|
|
640
|
+
if module_path.exists() or (root_path / f"{import_path}.js").exists() or (root_path / f"{import_path}.ts").exists() or (root_path / f"{import_path}.tsx").exists():
|
|
641
|
+
return False
|
|
642
|
+
|
|
643
|
+
elif language == 'rust':
|
|
644
|
+
# Rust uses crate:: for internal, external packages are just names
|
|
645
|
+
if 'crate::' in import_text or import_text.startswith('super::') or import_text.startswith('self::'):
|
|
646
|
+
return False
|
|
647
|
+
|
|
648
|
+
elif language in ['cpp', 'c']:
|
|
649
|
+
# #include "local.h" is internal, #include <system.h> is external
|
|
650
|
+
if import_text.startswith('#include "'):
|
|
651
|
+
return False # Local include
|
|
652
|
+
|
|
653
|
+
elif language == 'csharp':
|
|
654
|
+
# using System; is external, using MyProject.Models; is internal
|
|
655
|
+
if import_text.startswith('using '):
|
|
656
|
+
namespace = import_text[6:].strip().rstrip(';')
|
|
657
|
+
# Check if it's a standard .NET namespace
|
|
658
|
+
standard_namespaces = ['System', 'Microsoft', 'Newtonsoft', 'Serilog']
|
|
659
|
+
if any(namespace.startswith(std) for std in standard_namespaces):
|
|
660
|
+
return True
|
|
661
|
+
# Check if it matches a directory in the codebase
|
|
662
|
+
if (root_path / namespace.replace('.', '/')).exists():
|
|
663
|
+
return False
|
|
664
|
+
|
|
665
|
+
elif language == 'php':
|
|
666
|
+
# PHP: include/require with relative paths are internal
|
|
667
|
+
if import_text.startswith(('include ', 'include_once ', 'require ', 'require_once ')):
|
|
668
|
+
# Extract the path
|
|
669
|
+
parts = import_text.split()
|
|
670
|
+
if len(parts) >= 2:
|
|
671
|
+
path = parts[1].strip('"\'();')
|
|
672
|
+
# Relative paths are internal
|
|
673
|
+
if path.startswith('./') or path.startswith('../'):
|
|
674
|
+
return False
|
|
675
|
+
# Check if it exists in the codebase
|
|
676
|
+
if (root_path / path).exists():
|
|
677
|
+
return False
|
|
678
|
+
elif import_text.startswith('use '):
|
|
679
|
+
# use statements for namespaces
|
|
680
|
+
namespace = import_text[4:].strip().rstrip(';')
|
|
681
|
+
# Check if it's a standard PHP namespace
|
|
682
|
+
standard_namespaces = ['PHP', 'Symfony', 'Laravel', 'Doctrine']
|
|
683
|
+
if any(namespace.startswith(std) for std in standard_namespaces):
|
|
684
|
+
return True
|
|
685
|
+
# Check if it matches a directory in the codebase
|
|
686
|
+
if (root_path / namespace.replace('\\', '/')).exists():
|
|
687
|
+
return False
|
|
688
|
+
|
|
689
|
+
elif language == 'dart':
|
|
690
|
+
# Dart: package: imports are external, relative imports are internal
|
|
691
|
+
if import_text.startswith('import '):
|
|
692
|
+
# Extract the URI
|
|
693
|
+
if "'" in import_text:
|
|
694
|
+
uri = import_text.split("'")[1]
|
|
695
|
+
elif '"' in import_text:
|
|
696
|
+
uri = import_text.split('"')[1]
|
|
697
|
+
else:
|
|
698
|
+
return True
|
|
699
|
+
if uri.startswith('dart:') or uri.startswith('package:'):
|
|
700
|
+
return True
|
|
701
|
+
if uri.startswith('./') or uri.startswith('../') or uri.startswith('/'):
|
|
702
|
+
return False
|
|
703
|
+
# Check if it exists in the codebase
|
|
704
|
+
if (root_path / uri).exists():
|
|
705
|
+
return False
|
|
706
|
+
|
|
707
|
+
elif language == 'java':
|
|
708
|
+
# Java: import com.example.* is internal if com/example exists
|
|
709
|
+
if import_text.startswith('import '):
|
|
710
|
+
parts = import_text.split()
|
|
711
|
+
if len(parts) >= 2:
|
|
712
|
+
import_path = parts[1].rstrip(';').replace('.', '/')
|
|
713
|
+
if (root_path / import_path).exists() or (root_path / (import_path + '.java')).exists():
|
|
714
|
+
return False
|
|
715
|
+
|
|
716
|
+
elif language == 'kotlin':
|
|
717
|
+
# Kotlin: import is internal if the path exists in the codebase
|
|
718
|
+
if import_text.startswith('import '):
|
|
719
|
+
parts = import_text.split()
|
|
720
|
+
if len(parts) >= 2:
|
|
721
|
+
import_path = parts[1].replace('.', '/')
|
|
722
|
+
if (root_path / import_path).exists() or (root_path / (import_path + '.kt')).exists():
|
|
723
|
+
return False
|
|
724
|
+
|
|
725
|
+
elif language == 'zig':
|
|
726
|
+
# Zig: @import("file.zig") is internal if the file exists
|
|
727
|
+
if '@import' in import_text:
|
|
728
|
+
if '"' in import_text:
|
|
729
|
+
path = import_text.split('"')[1]
|
|
730
|
+
elif "'" in import_text:
|
|
731
|
+
path = import_text.split("'")[1]
|
|
732
|
+
else:
|
|
733
|
+
return True
|
|
734
|
+
if path.startswith('std') or path.startswith('builtin') or path.startswith('root'):
|
|
735
|
+
return True
|
|
736
|
+
if (root_path / path).exists():
|
|
737
|
+
return False
|
|
738
|
+
|
|
739
|
+
elif language == 'elixir':
|
|
740
|
+
# Elixir: alias MyApp.Foo is internal if lib/my_app/foo.ex exists
|
|
741
|
+
# The import text is just the module path (e.g. "Plug.Conn")
|
|
742
|
+
module_name = import_text.strip().rstrip('.')
|
|
743
|
+
if module_name:
|
|
744
|
+
import re
|
|
745
|
+
s1 = re.sub('([a-z0-9])([A-Z])', r'\1_\2', module_name)
|
|
746
|
+
s2 = re.sub('([A-Z]+)([A-Z][a-z])', r'\1_\2', s1)
|
|
747
|
+
snake = s2.lower()
|
|
748
|
+
base_path = root_path / 'lib' / snake.replace('.', '/')
|
|
749
|
+
if base_path.exists() or base_path.with_suffix('.ex').exists():
|
|
750
|
+
return False
|
|
751
|
+
|
|
752
|
+
return True # Default to external
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
def extract_function_calls(node, source_code, language):
|
|
756
|
+
"""Extract function calls from a node (iterative to avoid recursion limits).
|
|
757
|
+
|
|
758
|
+
Handles various call patterns:
|
|
759
|
+
- Direct calls: func()
|
|
760
|
+
- Method calls: obj.method(), self.method()
|
|
761
|
+
- Chained calls: obj.method().another()
|
|
762
|
+
- Attribute calls: module.func()
|
|
763
|
+
- Class instantiations: ClassName() (captured as function_call for dependency tracking)
|
|
764
|
+
"""
|
|
765
|
+
calls = []
|
|
766
|
+
|
|
767
|
+
call_types = {
|
|
768
|
+
'python': ['call'],
|
|
769
|
+
'go': ['call_expression'],
|
|
770
|
+
'javascript': ['call_expression'],
|
|
771
|
+
'typescript': ['call_expression'],
|
|
772
|
+
'tsx': ['call_expression'],
|
|
773
|
+
'csharp': ['invocation_expression'],
|
|
774
|
+
'rust': ['call_expression'],
|
|
775
|
+
'cpp': ['call_expression'],
|
|
776
|
+
'c': ['call_expression'],
|
|
777
|
+
'zig': ['call_expression'],
|
|
778
|
+
'elixir': ['call'],
|
|
779
|
+
'php': ['function_call_expression'],
|
|
780
|
+
'dart': ['expression_statement'],
|
|
781
|
+
'java': ['method_invocation', 'object_creation_expression'],
|
|
782
|
+
'kotlin': ['call_expression']
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
lang_call_types = call_types.get(language, ['call_expression'])
|
|
786
|
+
|
|
787
|
+
stack = [node]
|
|
788
|
+
while stack:
|
|
789
|
+
n = stack.pop()
|
|
790
|
+
if n.type in lang_call_types:
|
|
791
|
+
func_name = None
|
|
792
|
+
dep_type = 'function_call'
|
|
793
|
+
|
|
794
|
+
# Dart: expression_statement with identifier + selector(arguments)
|
|
795
|
+
if language == 'dart' and n.type == 'expression_statement':
|
|
796
|
+
has_args = False
|
|
797
|
+
method_name = None
|
|
798
|
+
first_ident = None
|
|
799
|
+
for child in n.children:
|
|
800
|
+
if child.type == 'identifier' and first_ident is None:
|
|
801
|
+
first_ident = extract_node_text(child, source_code)
|
|
802
|
+
elif child.type == 'selector':
|
|
803
|
+
for sel_child in child.children:
|
|
804
|
+
if sel_child.type == 'argument_part':
|
|
805
|
+
for arg_child in sel_child.children:
|
|
806
|
+
if arg_child.type == 'arguments':
|
|
807
|
+
has_args = True
|
|
808
|
+
break
|
|
809
|
+
elif sel_child.type == 'unconditional_assignable_selector':
|
|
810
|
+
for us_child in sel_child.children:
|
|
811
|
+
if us_child.type == 'identifier':
|
|
812
|
+
method_name = extract_node_text(us_child, source_code)
|
|
813
|
+
if has_args:
|
|
814
|
+
if method_name:
|
|
815
|
+
func_name = method_name
|
|
816
|
+
dep_type = 'method_call'
|
|
817
|
+
elif first_ident:
|
|
818
|
+
func_name = first_ident
|
|
819
|
+
dep_type = 'function_call'
|
|
820
|
+
if func_name:
|
|
821
|
+
calls.append({
|
|
822
|
+
'name': func_name,
|
|
823
|
+
'location': get_node_location(n),
|
|
824
|
+
'dependency_type': dep_type
|
|
825
|
+
})
|
|
826
|
+
continue
|
|
827
|
+
|
|
828
|
+
# Try to extract the function name from the call expression
|
|
829
|
+
for child in n.children:
|
|
830
|
+
# Direct identifier: func() or ClassName()
|
|
831
|
+
if child.type in ['identifier', 'simple_identifier']:
|
|
832
|
+
func_name = extract_node_text(child, source_code)
|
|
833
|
+
dep_type = 'function_call'
|
|
834
|
+
break
|
|
835
|
+
# Attribute access: obj.method or module.func
|
|
836
|
+
elif child.type in ['attribute', 'member_expression', 'field_expression', 'member_access_expression', 'field_access_expression', 'navigation_expression']:
|
|
837
|
+
func_name = extract_node_text(child, source_code)
|
|
838
|
+
dep_type = 'method_call'
|
|
839
|
+
break
|
|
840
|
+
# Selector expression (Go): obj.method()
|
|
841
|
+
elif child.type == 'selector_expression':
|
|
842
|
+
func_name = extract_node_text(child, source_code)
|
|
843
|
+
dep_type = 'method_call'
|
|
844
|
+
break
|
|
845
|
+
# Chained calls: obj.method().another()
|
|
846
|
+
elif child.type == 'call':
|
|
847
|
+
# This is a nested call, extract from the inner call
|
|
848
|
+
for grandchild in child.children:
|
|
849
|
+
if grandchild.type in ['identifier', 'simple_identifier', 'attribute', 'member_expression', 'field_access_expression', 'navigation_expression']:
|
|
850
|
+
func_name = extract_node_text(grandchild, source_code)
|
|
851
|
+
dep_type = 'method_call' if grandchild.type not in ['identifier', 'simple_identifier'] else 'function_call'
|
|
852
|
+
break
|
|
853
|
+
if func_name:
|
|
854
|
+
break
|
|
855
|
+
|
|
856
|
+
if func_name:
|
|
857
|
+
calls.append({
|
|
858
|
+
'name': func_name,
|
|
859
|
+
'location': get_node_location(n),
|
|
860
|
+
'dependency_type': dep_type
|
|
861
|
+
})
|
|
862
|
+
stack.extend(reversed(list(n.children)))
|
|
863
|
+
|
|
864
|
+
return calls
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
def extract_class_references(node, source_code, language):
|
|
868
|
+
"""Extract class references (instantiations, type annotations, etc.) from a node (iterative)."""
|
|
869
|
+
references = []
|
|
870
|
+
|
|
871
|
+
stack = [node]
|
|
872
|
+
while stack:
|
|
873
|
+
n = stack.pop()
|
|
874
|
+
|
|
875
|
+
if language == 'python':
|
|
876
|
+
if n.type == 'call':
|
|
877
|
+
for child in n.children:
|
|
878
|
+
if child.type == 'identifier':
|
|
879
|
+
name = extract_node_text(child, source_code)
|
|
880
|
+
if name and name[0].isupper():
|
|
881
|
+
references.append({
|
|
882
|
+
'name': name,
|
|
883
|
+
'location': get_node_location(child),
|
|
884
|
+
'dependency_type': 'class_reference'
|
|
885
|
+
})
|
|
886
|
+
break
|
|
887
|
+
elif n.type == 'type':
|
|
888
|
+
for child in n.children:
|
|
889
|
+
if child.type == 'identifier' or child.type == 'type':
|
|
890
|
+
name = extract_node_text(child, source_code)
|
|
891
|
+
if name and name[0].isupper():
|
|
892
|
+
references.append({
|
|
893
|
+
'name': name,
|
|
894
|
+
'location': get_node_location(child),
|
|
895
|
+
'dependency_type': 'class_reference'
|
|
896
|
+
})
|
|
897
|
+
|
|
898
|
+
elif language in ['javascript', 'typescript', 'tsx']:
|
|
899
|
+
if n.type == 'new_expression':
|
|
900
|
+
for child in n.children:
|
|
901
|
+
if child.type in ['identifier', 'member_expression']:
|
|
902
|
+
name = extract_node_text(child, source_code)
|
|
903
|
+
if name:
|
|
904
|
+
references.append({
|
|
905
|
+
'name': name,
|
|
906
|
+
'location': get_node_location(child),
|
|
907
|
+
'dependency_type': 'class_reference'
|
|
908
|
+
})
|
|
909
|
+
break
|
|
910
|
+
elif n.type == 'type_annotation':
|
|
911
|
+
for child in n.children:
|
|
912
|
+
if child.type in ['identifier', 'type_identifier']:
|
|
913
|
+
name = extract_node_text(child, source_code)
|
|
914
|
+
if name and name[0].isupper():
|
|
915
|
+
references.append({
|
|
916
|
+
'name': name,
|
|
917
|
+
'location': get_node_location(child),
|
|
918
|
+
'dependency_type': 'class_reference'
|
|
919
|
+
})
|
|
920
|
+
|
|
921
|
+
elif language == 'csharp':
|
|
922
|
+
if n.type == 'object_creation_expression':
|
|
923
|
+
for child in n.children:
|
|
924
|
+
if child.type == 'identifier':
|
|
925
|
+
name = extract_node_text(child, source_code)
|
|
926
|
+
if name:
|
|
927
|
+
references.append({
|
|
928
|
+
'name': name,
|
|
929
|
+
'location': get_node_location(child),
|
|
930
|
+
'dependency_type': 'class_reference'
|
|
931
|
+
})
|
|
932
|
+
break
|
|
933
|
+
|
|
934
|
+
elif language == 'go':
|
|
935
|
+
if n.type == 'composite_literal':
|
|
936
|
+
for child in n.children:
|
|
937
|
+
if child.type == 'identifier' or child.type == 'selector_expression':
|
|
938
|
+
name = extract_node_text(child, source_code)
|
|
939
|
+
if name and name[0].isupper():
|
|
940
|
+
references.append({
|
|
941
|
+
'name': name,
|
|
942
|
+
'location': get_node_location(child),
|
|
943
|
+
'dependency_type': 'class_reference'
|
|
944
|
+
})
|
|
945
|
+
break
|
|
946
|
+
|
|
947
|
+
elif language == 'rust':
|
|
948
|
+
if n.type == 'call_expression':
|
|
949
|
+
for child in n.children:
|
|
950
|
+
if child.type == 'identifier' or child.type == 'scoped_identifier':
|
|
951
|
+
name = extract_node_text(child, source_code)
|
|
952
|
+
if name and name[0].isupper():
|
|
953
|
+
references.append({
|
|
954
|
+
'name': name,
|
|
955
|
+
'location': get_node_location(child),
|
|
956
|
+
'dependency_type': 'class_reference'
|
|
957
|
+
})
|
|
958
|
+
break
|
|
959
|
+
|
|
960
|
+
elif language == 'php':
|
|
961
|
+
if n.type == 'new_expression':
|
|
962
|
+
for child in n.children:
|
|
963
|
+
if child.type in ['identifier', 'name']:
|
|
964
|
+
name = extract_node_text(child, source_code)
|
|
965
|
+
if name and name[0].isupper():
|
|
966
|
+
references.append({
|
|
967
|
+
'name': name,
|
|
968
|
+
'location': get_node_location(child),
|
|
969
|
+
'dependency_type': 'class_reference'
|
|
970
|
+
})
|
|
971
|
+
break
|
|
972
|
+
|
|
973
|
+
elif language == 'cpp':
|
|
974
|
+
if n.type == 'new_expression':
|
|
975
|
+
for child in n.children:
|
|
976
|
+
if child.type in ['identifier', 'type_identifier', 'qualified_identifier']:
|
|
977
|
+
name = extract_node_text(child, source_code)
|
|
978
|
+
if name:
|
|
979
|
+
references.append({
|
|
980
|
+
'name': name,
|
|
981
|
+
'location': get_node_location(child),
|
|
982
|
+
'dependency_type': 'class_reference'
|
|
983
|
+
})
|
|
984
|
+
break
|
|
985
|
+
elif n.type == 'class_specifier':
|
|
986
|
+
pass # Skip class definitions themselves
|
|
987
|
+
|
|
988
|
+
elif language == 'c':
|
|
989
|
+
if n.type == 'call_expression':
|
|
990
|
+
for child in n.children:
|
|
991
|
+
if child.type == 'identifier':
|
|
992
|
+
name = extract_node_text(child, source_code)
|
|
993
|
+
if name and name[0].isupper():
|
|
994
|
+
references.append({
|
|
995
|
+
'name': name,
|
|
996
|
+
'location': get_node_location(child),
|
|
997
|
+
'dependency_type': 'class_reference'
|
|
998
|
+
})
|
|
999
|
+
break
|
|
1000
|
+
|
|
1001
|
+
elif language == 'dart':
|
|
1002
|
+
if n.type in ('constructor_invocation', 'const_object_expression'):
|
|
1003
|
+
for child in n.children:
|
|
1004
|
+
if child.type in ['identifier', 'type_identifier']:
|
|
1005
|
+
name = extract_node_text(child, source_code)
|
|
1006
|
+
if name and name[0].isupper():
|
|
1007
|
+
references.append({
|
|
1008
|
+
'name': name,
|
|
1009
|
+
'location': get_node_location(child),
|
|
1010
|
+
'dependency_type': 'class_reference'
|
|
1011
|
+
})
|
|
1012
|
+
break
|
|
1013
|
+
elif n.type == 'type_annotation':
|
|
1014
|
+
for child in n.children:
|
|
1015
|
+
if child.type in ['identifier', 'type_identifier']:
|
|
1016
|
+
name = extract_node_text(child, source_code)
|
|
1017
|
+
if name and name[0].isupper():
|
|
1018
|
+
references.append({
|
|
1019
|
+
'name': name,
|
|
1020
|
+
'location': get_node_location(child),
|
|
1021
|
+
'dependency_type': 'class_reference'
|
|
1022
|
+
})
|
|
1023
|
+
|
|
1024
|
+
elif language == 'java':
|
|
1025
|
+
if n.type == 'object_creation_expression':
|
|
1026
|
+
for child in n.children:
|
|
1027
|
+
if child.type in ['type_identifier', 'identifier']:
|
|
1028
|
+
name = extract_node_text(child, source_code)
|
|
1029
|
+
if name:
|
|
1030
|
+
references.append({
|
|
1031
|
+
'name': name,
|
|
1032
|
+
'location': get_node_location(child),
|
|
1033
|
+
'dependency_type': 'class_reference'
|
|
1034
|
+
})
|
|
1035
|
+
break
|
|
1036
|
+
|
|
1037
|
+
elif language == 'kotlin':
|
|
1038
|
+
if n.type == 'constructor_invocation' or n.type == 'call_expression':
|
|
1039
|
+
for child in n.children:
|
|
1040
|
+
if child.type in ['type_identifier', 'identifier', 'simple_identifier', 'user_type']:
|
|
1041
|
+
name = extract_node_text(child, source_code)
|
|
1042
|
+
if name and name[0].isupper():
|
|
1043
|
+
references.append({
|
|
1044
|
+
'name': name,
|
|
1045
|
+
'location': get_node_location(child),
|
|
1046
|
+
'dependency_type': 'class_reference'
|
|
1047
|
+
})
|
|
1048
|
+
break
|
|
1049
|
+
|
|
1050
|
+
stack.extend(reversed(list(n.children)))
|
|
1051
|
+
|
|
1052
|
+
return references
|
|
1053
|
+
|
|
1054
|
+
|
|
1055
|
+
def extract_variable_references(node, source_code, language):
|
|
1056
|
+
"""Extract variable references from a node (iterative to avoid recursion limits)."""
|
|
1057
|
+
references = []
|
|
1058
|
+
|
|
1059
|
+
skip_types = {'function_definition', 'class_definition', 'function_declaration',
|
|
1060
|
+
'class_declaration', 'method_declaration', 'parameter', 'assignment',
|
|
1061
|
+
'variable_declaration',
|
|
1062
|
+
'function_signature', 'method_signature', 'constructor_declaration',
|
|
1063
|
+
'function_item', 'struct_item', 'enum_item', 'trait_item'}
|
|
1064
|
+
|
|
1065
|
+
stack = [node]
|
|
1066
|
+
while stack:
|
|
1067
|
+
n = stack.pop()
|
|
1068
|
+
|
|
1069
|
+
if n.type in skip_types:
|
|
1070
|
+
continue
|
|
1071
|
+
|
|
1072
|
+
if n.type in ['identifier', 'simple_identifier']:
|
|
1073
|
+
name = extract_node_text(n, source_code)
|
|
1074
|
+
if name and (name[0].islower() or name[0] == '_'):
|
|
1075
|
+
references.append({
|
|
1076
|
+
'name': name,
|
|
1077
|
+
'location': get_node_location(n),
|
|
1078
|
+
'dependency_type': 'variable_reference'
|
|
1079
|
+
})
|
|
1080
|
+
|
|
1081
|
+
stack.extend(reversed(list(n.children)))
|
|
1082
|
+
|
|
1083
|
+
return references
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
def create_parser(language_module):
|
|
1087
|
+
"""Create a tree-sitter parser for a language."""
|
|
1088
|
+
from tree_sitter import Language, Parser
|
|
1089
|
+
|
|
1090
|
+
# Already a Language object (e.g., from tree-sitter-language-pack)
|
|
1091
|
+
if isinstance(language_module, Language):
|
|
1092
|
+
return Parser(language_module)
|
|
1093
|
+
|
|
1094
|
+
# Callable that returns the language pointer
|
|
1095
|
+
if callable(language_module):
|
|
1096
|
+
lang_obj = language_module()
|
|
1097
|
+
|
|
1098
|
+
# New API: PyCapsule — wrap in Language() then pass to Parser
|
|
1099
|
+
if isinstance(lang_obj, int):
|
|
1100
|
+
# Old-style int pointer — try tree_sitter_language_pack to avoid deprecation
|
|
1101
|
+
# This shouldn't normally happen if language_config uses the right modules
|
|
1102
|
+
return Parser(Language(lang_obj))
|
|
1103
|
+
|
|
1104
|
+
# PyCapsule (tree-sitter >= 0.22) or Language object
|
|
1105
|
+
if isinstance(lang_obj, Language):
|
|
1106
|
+
return Parser(lang_obj)
|
|
1107
|
+
|
|
1108
|
+
# PyCapsule — wrap in Language()
|
|
1109
|
+
return Parser(Language(lang_obj))
|
|
1110
|
+
|
|
1111
|
+
# Fallback: treat as raw pointer
|
|
1112
|
+
return Parser(Language(language_module))
|