codegraphcontext-rust 0.5.5__cp311-cp311-win_amd64.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.
- codegraphcontext/__init__.py +2 -0
- codegraphcontext/__main__.py +5 -0
- codegraphcontext/_cgc_rust.cp311-win_amd64.pyd +0 -0
- codegraphcontext/cli/__init__.py +1 -0
- codegraphcontext/cli/cli_helpers.py +774 -0
- codegraphcontext/cli/config_manager.py +1051 -0
- codegraphcontext/cli/main.py +2386 -0
- codegraphcontext/cli/registry_commands.py +404 -0
- codegraphcontext/cli/setup_macos.py +93 -0
- codegraphcontext/cli/setup_wizard.py +991 -0
- codegraphcontext/cli/visualizer.py +50 -0
- codegraphcontext/core/__init__.py +165 -0
- codegraphcontext/core/bundle_registry.py +181 -0
- codegraphcontext/core/cgc_bundle.py +858 -0
- codegraphcontext/core/cgcignore.py +118 -0
- codegraphcontext/core/database.py +273 -0
- codegraphcontext/core/database_falkordb.py +480 -0
- codegraphcontext/core/database_falkordb_remote.py +199 -0
- codegraphcontext/core/database_kuzu.py +636 -0
- codegraphcontext/core/falkor_worker.py +132 -0
- codegraphcontext/core/jobs.py +132 -0
- codegraphcontext/core/watcher.py +260 -0
- codegraphcontext/prompts.py +125 -0
- codegraphcontext/server.py +471 -0
- codegraphcontext/tool_definitions.py +220 -0
- codegraphcontext/tools/__init__.py +0 -0
- codegraphcontext/tools/advanced_language_query_tool.py +103 -0
- codegraphcontext/tools/code_finder.py +1160 -0
- codegraphcontext/tools/graph_builder.py +337 -0
- codegraphcontext/tools/handlers/analysis_handlers.py +114 -0
- codegraphcontext/tools/handlers/indexing_handlers.py +116 -0
- codegraphcontext/tools/handlers/management_handlers.py +339 -0
- codegraphcontext/tools/handlers/query_handlers.py +83 -0
- codegraphcontext/tools/handlers/watcher_handlers.py +83 -0
- codegraphcontext/tools/indexing/__init__.py +1 -0
- codegraphcontext/tools/indexing/constants.py +25 -0
- codegraphcontext/tools/indexing/discovery.py +64 -0
- codegraphcontext/tools/indexing/engine.py +109 -0
- codegraphcontext/tools/indexing/persistence/__init__.py +3 -0
- codegraphcontext/tools/indexing/persistence/parquet_loader.py +643 -0
- codegraphcontext/tools/indexing/persistence/writer.py +1114 -0
- codegraphcontext/tools/indexing/pipeline.py +171 -0
- codegraphcontext/tools/indexing/pre_scan.py +105 -0
- codegraphcontext/tools/indexing/resolution/__init__.py +8 -0
- codegraphcontext/tools/indexing/resolution/calls.py +253 -0
- codegraphcontext/tools/indexing/resolution/inheritance.py +91 -0
- codegraphcontext/tools/indexing/sanitize.py +41 -0
- codegraphcontext/tools/indexing/schema.py +79 -0
- codegraphcontext/tools/indexing/schema_contract.py +44 -0
- codegraphcontext/tools/indexing/scip_pipeline.py +140 -0
- codegraphcontext/tools/languages/c.py +563 -0
- codegraphcontext/tools/languages/cpp.py +616 -0
- codegraphcontext/tools/languages/csharp.py +551 -0
- codegraphcontext/tools/languages/dart.py +378 -0
- codegraphcontext/tools/languages/elixir.py +460 -0
- codegraphcontext/tools/languages/go.py +499 -0
- codegraphcontext/tools/languages/haskell.py +426 -0
- codegraphcontext/tools/languages/java.py +500 -0
- codegraphcontext/tools/languages/javascript.py +589 -0
- codegraphcontext/tools/languages/kotlin.py +640 -0
- codegraphcontext/tools/languages/perl.py +260 -0
- codegraphcontext/tools/languages/php.py +519 -0
- codegraphcontext/tools/languages/python.py +576 -0
- codegraphcontext/tools/languages/ruby.py +537 -0
- codegraphcontext/tools/languages/rust.py +295 -0
- codegraphcontext/tools/languages/scala.py +519 -0
- codegraphcontext/tools/languages/swift.py +491 -0
- codegraphcontext/tools/languages/typescript.py +575 -0
- codegraphcontext/tools/languages/typescriptjsx.py +151 -0
- codegraphcontext/tools/package_resolver.py +472 -0
- codegraphcontext/tools/query_tool_languages/c_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/cpp_toolkit.py +87 -0
- codegraphcontext/tools/query_tool_languages/csharp_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/dart_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/go_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/haskell_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/java_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/javascript_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/perl_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/python_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/ruby_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/rust_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/scala_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/swift_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/typescript_toolkit.py +5 -0
- codegraphcontext/tools/scip_indexer.py +467 -0
- codegraphcontext/tools/scip_pb2.py +2455 -0
- codegraphcontext/tools/system.py +133 -0
- codegraphcontext/tools/tree_sitter_parser.py +103 -0
- codegraphcontext/utils/debug_log.py +90 -0
- codegraphcontext/utils/path_ignore.py +55 -0
- codegraphcontext/utils/repo_path.py +27 -0
- codegraphcontext/utils/tree_sitter_manager.py +285 -0
- codegraphcontext/utils/visualize_graph.py +0 -0
- codegraphcontext/viz/server.py +282 -0
- codegraphcontext_rust-0.5.5.dist-info/METADATA +537 -0
- codegraphcontext_rust-0.5.5.dist-info/RECORD +101 -0
- codegraphcontext_rust-0.5.5.dist-info/WHEEL +4 -0
- codegraphcontext_rust-0.5.5.dist-info/entry_points.txt +3 -0
- codegraphcontext_rust-0.5.5.dist-info/licenses/LICENSE +21 -0
- codegraphcontext_rust-0.5.5.dist-info/sboms/cgc-python.cyclonedx.json +2333 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any, Dict, Optional, Tuple
|
|
3
|
+
import re
|
|
4
|
+
from codegraphcontext.utils.debug_log import debug_log, info_logger, error_logger, warning_logger
|
|
5
|
+
from codegraphcontext.utils.tree_sitter_manager import execute_query
|
|
6
|
+
|
|
7
|
+
# Reference: https://github.com/tree-sitter/tree-sitter-php/blob/master/queries/tags.scm
|
|
8
|
+
PHP_QUERIES = {
|
|
9
|
+
"functions": """
|
|
10
|
+
(function_definition
|
|
11
|
+
name: (name) @name
|
|
12
|
+
parameters: (formal_parameters) @params
|
|
13
|
+
) @function_node
|
|
14
|
+
|
|
15
|
+
(method_declaration
|
|
16
|
+
name: (name) @name
|
|
17
|
+
parameters: (formal_parameters) @params
|
|
18
|
+
) @function_node
|
|
19
|
+
""",
|
|
20
|
+
"classes": """
|
|
21
|
+
(class_declaration
|
|
22
|
+
name: (name) @name
|
|
23
|
+
) @class
|
|
24
|
+
|
|
25
|
+
(interface_declaration
|
|
26
|
+
name: (name) @name
|
|
27
|
+
) @interface
|
|
28
|
+
|
|
29
|
+
(trait_declaration
|
|
30
|
+
name: (name) @name
|
|
31
|
+
) @trait
|
|
32
|
+
""",
|
|
33
|
+
"imports": """
|
|
34
|
+
(use_declaration) @import
|
|
35
|
+
""",
|
|
36
|
+
"calls": """
|
|
37
|
+
(function_call_expression
|
|
38
|
+
function: [
|
|
39
|
+
(qualified_name) @name
|
|
40
|
+
(name) @name
|
|
41
|
+
]
|
|
42
|
+
) @call_node
|
|
43
|
+
|
|
44
|
+
(member_call_expression
|
|
45
|
+
name: (name) @name
|
|
46
|
+
) @call_node
|
|
47
|
+
|
|
48
|
+
(scoped_call_expression
|
|
49
|
+
name: (name) @name
|
|
50
|
+
) @call_node
|
|
51
|
+
|
|
52
|
+
(object_creation_expression) @call_node
|
|
53
|
+
""",
|
|
54
|
+
"variables": """
|
|
55
|
+
(variable_name) @variable
|
|
56
|
+
""",
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
class PhpTreeSitterParser:
|
|
60
|
+
def __init__(self, generic_parser_wrapper: Any):
|
|
61
|
+
self.generic_parser_wrapper = generic_parser_wrapper
|
|
62
|
+
self.language_name = "php"
|
|
63
|
+
self.language = generic_parser_wrapper.language
|
|
64
|
+
self.parser = generic_parser_wrapper.parser
|
|
65
|
+
|
|
66
|
+
def parse(self, path: Path, is_dependency: bool = False, index_source: bool = False) -> Dict[str, Any]:
|
|
67
|
+
try:
|
|
68
|
+
self.index_source = index_source
|
|
69
|
+
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
70
|
+
source_code = f.read()
|
|
71
|
+
|
|
72
|
+
if not source_code.strip():
|
|
73
|
+
warning_logger(f"Empty or whitespace-only file: {path}")
|
|
74
|
+
return {
|
|
75
|
+
"path": str(path),
|
|
76
|
+
"functions": [],
|
|
77
|
+
"classes": [],
|
|
78
|
+
"interfaces": [],
|
|
79
|
+
"traits": [],
|
|
80
|
+
"variables": [],
|
|
81
|
+
"imports": [],
|
|
82
|
+
"function_calls": [],
|
|
83
|
+
"is_dependency": is_dependency,
|
|
84
|
+
"lang": self.language_name,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
tree = self.parser.parse(bytes(source_code, "utf8"))
|
|
88
|
+
|
|
89
|
+
parsed_functions = []
|
|
90
|
+
parsed_classes = []
|
|
91
|
+
parsed_interfaces = []
|
|
92
|
+
parsed_traits = []
|
|
93
|
+
parsed_variables = []
|
|
94
|
+
parsed_imports = []
|
|
95
|
+
parsed_calls = []
|
|
96
|
+
|
|
97
|
+
for capture_name, query in PHP_QUERIES.items():
|
|
98
|
+
results = execute_query(self.language, query, tree.root_node)
|
|
99
|
+
|
|
100
|
+
if capture_name == "functions":
|
|
101
|
+
parsed_functions = self._parse_functions(results, source_code, path)
|
|
102
|
+
elif capture_name == "classes":
|
|
103
|
+
# We group classes, interfaces, traits here, but separating them is cleaner
|
|
104
|
+
# Wait, my query combines them. I should verify results.
|
|
105
|
+
# execute_query returns (node, capture_name)
|
|
106
|
+
# I can filter inside _parse_classes
|
|
107
|
+
parsed_classes, parsed_interfaces, parsed_traits = self._parse_types(results, source_code, path)
|
|
108
|
+
elif capture_name == "imports":
|
|
109
|
+
parsed_imports = self._parse_imports(results, source_code)
|
|
110
|
+
elif capture_name == "calls":
|
|
111
|
+
parsed_calls = self._parse_calls(results, source_code)
|
|
112
|
+
elif capture_name == "variables":
|
|
113
|
+
parsed_variables = self._parse_variables(results, source_code, path)
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
"path": str(path),
|
|
117
|
+
"functions": parsed_functions,
|
|
118
|
+
"classes": parsed_classes,
|
|
119
|
+
"interfaces": parsed_interfaces,
|
|
120
|
+
"traits": parsed_traits,
|
|
121
|
+
"variables": parsed_variables,
|
|
122
|
+
"imports": parsed_imports,
|
|
123
|
+
"function_calls": parsed_calls,
|
|
124
|
+
"is_dependency": is_dependency,
|
|
125
|
+
"lang": self.language_name,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
error_logger(f"Error parsing PHP file {path}: {e}")
|
|
130
|
+
return {
|
|
131
|
+
"path": str(path),
|
|
132
|
+
"functions": [],
|
|
133
|
+
"classes": [],
|
|
134
|
+
"interfaces": [],
|
|
135
|
+
"traits": [],
|
|
136
|
+
"variables": [],
|
|
137
|
+
"imports": [],
|
|
138
|
+
"function_calls": [],
|
|
139
|
+
"is_dependency": is_dependency,
|
|
140
|
+
"lang": self.language_name,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
def _get_parent_context(self, node: Any) -> Tuple[Optional[str], Optional[str], Optional[int]]:
|
|
144
|
+
curr = node.parent
|
|
145
|
+
while curr:
|
|
146
|
+
if curr.type in ("function_definition", "method_declaration", "class_declaration", "interface_declaration", "trait_declaration"):
|
|
147
|
+
name_node = curr.child_by_field_name("name")
|
|
148
|
+
return (
|
|
149
|
+
self._get_node_text(name_node) if name_node else None,
|
|
150
|
+
curr.type,
|
|
151
|
+
curr.start_point[0] + 1,
|
|
152
|
+
)
|
|
153
|
+
curr = curr.parent
|
|
154
|
+
return None, None, None
|
|
155
|
+
|
|
156
|
+
def _get_node_text(self, node: Any) -> str:
|
|
157
|
+
if not node: return ""
|
|
158
|
+
return node.text.decode("utf-8")
|
|
159
|
+
|
|
160
|
+
def _parse_functions(self, captures: list, source_code: str, path: Path) -> list[Dict[str, Any]]:
|
|
161
|
+
functions = []
|
|
162
|
+
seen_nodes = set()
|
|
163
|
+
|
|
164
|
+
for node, capture_name in captures:
|
|
165
|
+
if capture_name == "function_node":
|
|
166
|
+
node_id = (node.start_byte, node.end_byte, node.type)
|
|
167
|
+
if node_id in seen_nodes:
|
|
168
|
+
continue
|
|
169
|
+
seen_nodes.add(node_id)
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
start_line = node.start_point[0] + 1
|
|
173
|
+
end_line = node.end_point[0] + 1
|
|
174
|
+
|
|
175
|
+
name_node = node.child_by_field_name("name")
|
|
176
|
+
if name_node:
|
|
177
|
+
func_name = self._get_node_text(name_node)
|
|
178
|
+
|
|
179
|
+
params_node = node.child_by_field_name("parameters")
|
|
180
|
+
parameters = []
|
|
181
|
+
if params_node:
|
|
182
|
+
# PHP parameters: function($a, $b)
|
|
183
|
+
for child in params_node.children:
|
|
184
|
+
if "variable_name" in child.type or "simple_parameter" in child.type:
|
|
185
|
+
# Extract variable name from simple_parameter
|
|
186
|
+
var_node = child if "variable_name" in child.type else child.child_by_field_name("name")
|
|
187
|
+
if var_node:
|
|
188
|
+
parameters.append(self._get_node_text(var_node))
|
|
189
|
+
|
|
190
|
+
source_text = self._get_node_text(node)
|
|
191
|
+
|
|
192
|
+
# Get class context
|
|
193
|
+
context_name, context_type, context_line = self._get_parent_context(node)
|
|
194
|
+
|
|
195
|
+
func_data = {
|
|
196
|
+
"name": func_name,
|
|
197
|
+
"parameters": parameters,
|
|
198
|
+
"line_number": start_line,
|
|
199
|
+
"end_line": end_line,
|
|
200
|
+
"path": str(path),
|
|
201
|
+
"lang": self.language_name,
|
|
202
|
+
"context": context_name,
|
|
203
|
+
"context_type": context_type,
|
|
204
|
+
"class_context": context_name if context_type and ("class" in context_type or "interface" in context_type or "trait" in context_type) else None
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if self.index_source:
|
|
208
|
+
func_data["source"] = source_text
|
|
209
|
+
|
|
210
|
+
functions.append(func_data)
|
|
211
|
+
|
|
212
|
+
except Exception as e:
|
|
213
|
+
error_logger(f"Error parsing function in {path}: {e}")
|
|
214
|
+
continue
|
|
215
|
+
|
|
216
|
+
return functions
|
|
217
|
+
|
|
218
|
+
def _parse_types(self, captures: list, source_code: str, path: Path) -> Tuple[list, list, list]:
|
|
219
|
+
classes = []
|
|
220
|
+
interfaces = []
|
|
221
|
+
traits = []
|
|
222
|
+
seen_nodes = set()
|
|
223
|
+
|
|
224
|
+
for node, capture_name in captures:
|
|
225
|
+
if capture_name in ("class", "interface", "trait"):
|
|
226
|
+
node_id = (node.start_byte, node.end_byte, node.type)
|
|
227
|
+
if node_id in seen_nodes:
|
|
228
|
+
continue
|
|
229
|
+
seen_nodes.add(node_id)
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
start_line = node.start_point[0] + 1
|
|
233
|
+
end_line = node.end_point[0] + 1
|
|
234
|
+
|
|
235
|
+
name_node = node.child_by_field_name("name")
|
|
236
|
+
if name_node:
|
|
237
|
+
type_name = self._get_node_text(name_node)
|
|
238
|
+
source_text = self._get_node_text(node)
|
|
239
|
+
|
|
240
|
+
bases = []
|
|
241
|
+
# Look for extends/implements
|
|
242
|
+
base_clause_node = node.child_by_field_name('base_clause') # extends
|
|
243
|
+
interfaces_clause_node = node.child_by_field_name('interfaces_clause') # implements
|
|
244
|
+
|
|
245
|
+
if base_clause_node:
|
|
246
|
+
# Children of base_clause contain identifiers
|
|
247
|
+
for child in base_clause_node.children:
|
|
248
|
+
if child.type in ('name', 'qualified_name'):
|
|
249
|
+
bases.append(self._get_node_text(child))
|
|
250
|
+
|
|
251
|
+
if interfaces_clause_node:
|
|
252
|
+
for child in interfaces_clause_node.children:
|
|
253
|
+
if child.type in ('name', 'qualified_name'):
|
|
254
|
+
bases.append(self._get_node_text(child))
|
|
255
|
+
|
|
256
|
+
type_data = {
|
|
257
|
+
"name": type_name,
|
|
258
|
+
"line_number": start_line,
|
|
259
|
+
"end_line": end_line,
|
|
260
|
+
"bases": bases,
|
|
261
|
+
"path": str(path),
|
|
262
|
+
"lang": self.language_name,
|
|
263
|
+
}
|
|
264
|
+
if self.index_source:
|
|
265
|
+
type_data["source"] = source_text
|
|
266
|
+
|
|
267
|
+
if capture_name == "class":
|
|
268
|
+
classes.append(type_data)
|
|
269
|
+
elif capture_name == "interface":
|
|
270
|
+
interfaces.append(type_data)
|
|
271
|
+
elif capture_name == "trait":
|
|
272
|
+
traits.append(type_data)
|
|
273
|
+
|
|
274
|
+
except Exception as e:
|
|
275
|
+
error_logger(f"Error parsing type in {path}: {e}")
|
|
276
|
+
continue
|
|
277
|
+
|
|
278
|
+
return classes, interfaces, traits
|
|
279
|
+
|
|
280
|
+
def _parse_variables(self, captures: list, source_code: str, path: Path) -> list[Dict[str, Any]]:
|
|
281
|
+
variables = []
|
|
282
|
+
seen_vars = set()
|
|
283
|
+
|
|
284
|
+
for node, capture_name in captures:
|
|
285
|
+
if capture_name == "variable":
|
|
286
|
+
try:
|
|
287
|
+
var_name = self._get_node_text(node)
|
|
288
|
+
start_line = node.start_point[0] + 1
|
|
289
|
+
|
|
290
|
+
start_byte = node.start_byte
|
|
291
|
+
if start_byte in seen_vars:
|
|
292
|
+
continue
|
|
293
|
+
seen_vars.add(start_byte)
|
|
294
|
+
|
|
295
|
+
ctx_name, ctx_type, ctx_line = self._get_parent_context(node)
|
|
296
|
+
|
|
297
|
+
# Infer type from assignment
|
|
298
|
+
inferred_type = "mixed"
|
|
299
|
+
parent = node.parent
|
|
300
|
+
if parent and parent.type == 'assignment_expression':
|
|
301
|
+
# $var = new Class();
|
|
302
|
+
left = parent.child_by_field_name('left')
|
|
303
|
+
right = parent.child_by_field_name('right')
|
|
304
|
+
|
|
305
|
+
# Ensure we are looking at the left side variable
|
|
306
|
+
if left == node and right and right.type == 'object_creation_expression':
|
|
307
|
+
# Extract class name from right side
|
|
308
|
+
for child in right.children:
|
|
309
|
+
if child.type in ('name', 'qualified_name'):
|
|
310
|
+
inferred_type = self._get_node_text(child)
|
|
311
|
+
break
|
|
312
|
+
|
|
313
|
+
variables.append({
|
|
314
|
+
"name": var_name,
|
|
315
|
+
"type": inferred_type,
|
|
316
|
+
"line_number": start_line,
|
|
317
|
+
"path": str(path),
|
|
318
|
+
"lang": self.language_name,
|
|
319
|
+
"context": ctx_name,
|
|
320
|
+
"class_context": ctx_name if ctx_type and ("class" in ctx_type or "interface" in ctx_type or "trait" in ctx_type) else None
|
|
321
|
+
})
|
|
322
|
+
except Exception as e:
|
|
323
|
+
continue
|
|
324
|
+
|
|
325
|
+
return variables
|
|
326
|
+
|
|
327
|
+
def _parse_imports(self, captures: list, source_code: str) -> list[dict]:
|
|
328
|
+
imports = []
|
|
329
|
+
|
|
330
|
+
for node, capture_name in captures:
|
|
331
|
+
if capture_name == "import":
|
|
332
|
+
try:
|
|
333
|
+
import_text = self._get_node_text(node)
|
|
334
|
+
# use Foo\Bar as Baz;
|
|
335
|
+
# Node usually has children: name (qualified_name), optional alias
|
|
336
|
+
|
|
337
|
+
name_node = None
|
|
338
|
+
alias_node = None
|
|
339
|
+
|
|
340
|
+
for child in node.children:
|
|
341
|
+
if child.type == "qualified_name" or child.type == "name":
|
|
342
|
+
name_node = child
|
|
343
|
+
# Alias in PHP: use X as Y; The 'as' is usually implicit structure or explicit?
|
|
344
|
+
# Tree sitter grammar: use_declaration -> use_clause -> (use_as_clause (qualified_name) (name))
|
|
345
|
+
|
|
346
|
+
# Assuming simple handling for now, extracting string from text
|
|
347
|
+
# Regex might be safer given tree complexity for `use`
|
|
348
|
+
import_match = re.search(r'use\s+([\w\\]+)(?:\s+as\s+(\w+))?', import_text)
|
|
349
|
+
if import_match:
|
|
350
|
+
import_path = import_match.group(1).strip()
|
|
351
|
+
alias = import_match.group(2).strip() if import_match.group(2) else None
|
|
352
|
+
|
|
353
|
+
import_data = {
|
|
354
|
+
"name": import_path,
|
|
355
|
+
"full_import_name": import_text,
|
|
356
|
+
"line_number": node.start_point[0] + 1,
|
|
357
|
+
"alias": alias,
|
|
358
|
+
"context": (None, None),
|
|
359
|
+
"lang": self.language_name,
|
|
360
|
+
"is_dependency": False,
|
|
361
|
+
}
|
|
362
|
+
imports.append(import_data)
|
|
363
|
+
except Exception as e:
|
|
364
|
+
error_logger(f"Error parsing import: {e}")
|
|
365
|
+
continue
|
|
366
|
+
|
|
367
|
+
return imports
|
|
368
|
+
|
|
369
|
+
def _parse_calls(self, captures: list, source_code: str) -> list[dict]:
|
|
370
|
+
calls = []
|
|
371
|
+
seen_calls = set()
|
|
372
|
+
|
|
373
|
+
for node, capture_name in captures:
|
|
374
|
+
# For object_creation_expression without @name capture on inner node, we catch the whole node as @call_node
|
|
375
|
+
# But the 'calls' query uses @name for function/method calls on the identifier, and @call_node for full expression.
|
|
376
|
+
# My query change: (object_creation_expression) @call_node
|
|
377
|
+
# This means for obj creation, I won't get a separate @name capture. I need to iterate @call_node captures too?
|
|
378
|
+
# actually execute_query returns (node, capture_name).
|
|
379
|
+
|
|
380
|
+
# Let's handle 'name' capture which gives us the function name
|
|
381
|
+
if capture_name == "name":
|
|
382
|
+
try:
|
|
383
|
+
call_name = self._get_node_text(node)
|
|
384
|
+
line_number = node.start_point[0] + 1
|
|
385
|
+
|
|
386
|
+
# Ensure we identify the full call node
|
|
387
|
+
call_node = node.parent
|
|
388
|
+
while call_node and call_node.type not in ("function_call_expression", "member_call_expression", "scoped_call_expression"):
|
|
389
|
+
call_node = call_node.parent
|
|
390
|
+
|
|
391
|
+
if not call_node:
|
|
392
|
+
continue # It might be a name inside object creation or something we handle otherwise
|
|
393
|
+
|
|
394
|
+
# Avoid duplicates
|
|
395
|
+
call_key = f"{call_name}_{line_number}"
|
|
396
|
+
if call_key in seen_calls:
|
|
397
|
+
continue
|
|
398
|
+
seen_calls.add(call_key)
|
|
399
|
+
|
|
400
|
+
# Extract arguments
|
|
401
|
+
args = []
|
|
402
|
+
args_node = call_node.child_by_field_name('arguments')
|
|
403
|
+
if args_node:
|
|
404
|
+
for arg in args_node.children:
|
|
405
|
+
if arg.type not in ('(', ')', ','):
|
|
406
|
+
args.append(self._get_node_text(arg))
|
|
407
|
+
|
|
408
|
+
full_name = call_name # Default
|
|
409
|
+
if call_node.type == 'member_call_expression':
|
|
410
|
+
# $obj->method()
|
|
411
|
+
obj_node = call_node.child_by_field_name('object')
|
|
412
|
+
if obj_node:
|
|
413
|
+
receiver = self._get_node_text(obj_node)
|
|
414
|
+
# Normalize -> to . for graph builder compatibility
|
|
415
|
+
full_name = f"{receiver}.{call_name}"
|
|
416
|
+
elif call_node.type == 'scoped_call_expression':
|
|
417
|
+
# Class::method()
|
|
418
|
+
scope_node = call_node.child_by_field_name('scope')
|
|
419
|
+
if scope_node:
|
|
420
|
+
receiver = self._get_node_text(scope_node)
|
|
421
|
+
# Normalize :: to . for graph builder compatibility
|
|
422
|
+
full_name = f"{receiver}.{call_name}"
|
|
423
|
+
|
|
424
|
+
ctx_name, ctx_type, ctx_line = self._get_parent_context(node)
|
|
425
|
+
|
|
426
|
+
call_data = {
|
|
427
|
+
"name": call_name,
|
|
428
|
+
"full_name": full_name,
|
|
429
|
+
"line_number": line_number,
|
|
430
|
+
"args": args,
|
|
431
|
+
"inferred_obj_type": None,
|
|
432
|
+
"context": (ctx_name, ctx_type, ctx_line),
|
|
433
|
+
"class_context": (ctx_name, ctx_line) if ctx_type and ("class" in ctx_type or "interface" in ctx_type or "trait" in ctx_type) else (None, None),
|
|
434
|
+
"lang": self.language_name,
|
|
435
|
+
"is_dependency": False,
|
|
436
|
+
}
|
|
437
|
+
calls.append(call_data)
|
|
438
|
+
except Exception as e:
|
|
439
|
+
error_logger(f"Error parsing call: {e}")
|
|
440
|
+
continue
|
|
441
|
+
|
|
442
|
+
# Handle object creation separately as capture is on the whole node
|
|
443
|
+
elif capture_name == "call_node" and node.type == "object_creation_expression":
|
|
444
|
+
try:
|
|
445
|
+
line_number = node.start_point[0] + 1
|
|
446
|
+
|
|
447
|
+
# Find class name (child not named 'arguments')
|
|
448
|
+
class_name = "Unknown"
|
|
449
|
+
for child in node.children:
|
|
450
|
+
if child.type in ('name', 'qualified_name'):
|
|
451
|
+
class_name = self._get_node_text(child)
|
|
452
|
+
break
|
|
453
|
+
if child.type == "variable_name": # dynamic new $class()
|
|
454
|
+
class_name = self._get_node_text(child)
|
|
455
|
+
break
|
|
456
|
+
|
|
457
|
+
call_key = f"new {class_name}_{line_number}"
|
|
458
|
+
if call_key in seen_calls:
|
|
459
|
+
continue
|
|
460
|
+
seen_calls.add(call_key)
|
|
461
|
+
|
|
462
|
+
args = []
|
|
463
|
+
args_node = node.child_by_field_name('arguments')
|
|
464
|
+
if args_node:
|
|
465
|
+
for arg in args_node.children:
|
|
466
|
+
if arg.type not in ('(', ')', ','):
|
|
467
|
+
args.append(self._get_node_text(arg))
|
|
468
|
+
|
|
469
|
+
full_name = class_name # For GraphBuilder to link to Class
|
|
470
|
+
|
|
471
|
+
ctx_name, ctx_type, ctx_line = self._get_parent_context(node)
|
|
472
|
+
|
|
473
|
+
call_data = {
|
|
474
|
+
"name": class_name,
|
|
475
|
+
"full_name": full_name, # Usually we want the class name here so GB can link to Class node
|
|
476
|
+
"line_number": line_number,
|
|
477
|
+
"args": args,
|
|
478
|
+
"inferred_obj_type": None,
|
|
479
|
+
"context": (ctx_name, ctx_type, ctx_line),
|
|
480
|
+
"class_context": (ctx_name, ctx_line) if ctx_type and ("class" in ctx_type or "interface" in ctx_type or "trait" in ctx_type) else (None, None),
|
|
481
|
+
"lang": self.language_name,
|
|
482
|
+
"is_dependency": False,
|
|
483
|
+
}
|
|
484
|
+
calls.append(call_data)
|
|
485
|
+
except Exception:
|
|
486
|
+
continue
|
|
487
|
+
|
|
488
|
+
return calls
|
|
489
|
+
|
|
490
|
+
def pre_scan_php(files: list[Path], parser_wrapper) -> dict:
|
|
491
|
+
name_to_files = {}
|
|
492
|
+
for path in files:
|
|
493
|
+
try:
|
|
494
|
+
with open(path, "r", encoding="utf-8", errors="ignore") as f:
|
|
495
|
+
content = f.read()
|
|
496
|
+
# Extract class, interface, trait, and function names
|
|
497
|
+
patterns = [
|
|
498
|
+
r"(?:class|interface|trait)\s+(\w+)",
|
|
499
|
+
r"function\s+(\w+)\s*\(",
|
|
500
|
+
]
|
|
501
|
+
for pattern in patterns:
|
|
502
|
+
for match in re.finditer(pattern, content):
|
|
503
|
+
name = match.group(1)
|
|
504
|
+
if name not in name_to_files:
|
|
505
|
+
name_to_files[name] = []
|
|
506
|
+
name_to_files[name].append(str(path))
|
|
507
|
+
# Extract namespace for FQN mapping
|
|
508
|
+
ns_match = re.search(r"namespace\s+([\w\\]+)", content)
|
|
509
|
+
if ns_match:
|
|
510
|
+
namespace = ns_match.group(1)
|
|
511
|
+
for pattern in [r"(?:class|interface|trait)\s+(\w+)"]:
|
|
512
|
+
for match in re.finditer(pattern, content):
|
|
513
|
+
fqn = f"{namespace}\\{match.group(1)}"
|
|
514
|
+
if fqn not in name_to_files:
|
|
515
|
+
name_to_files[fqn] = []
|
|
516
|
+
name_to_files[fqn].append(str(path))
|
|
517
|
+
except Exception:
|
|
518
|
+
pass
|
|
519
|
+
return name_to_files
|