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.
Files changed (101) hide show
  1. codegraphcontext/__init__.py +2 -0
  2. codegraphcontext/__main__.py +5 -0
  3. codegraphcontext/_cgc_rust.cp311-win_amd64.pyd +0 -0
  4. codegraphcontext/cli/__init__.py +1 -0
  5. codegraphcontext/cli/cli_helpers.py +774 -0
  6. codegraphcontext/cli/config_manager.py +1051 -0
  7. codegraphcontext/cli/main.py +2386 -0
  8. codegraphcontext/cli/registry_commands.py +404 -0
  9. codegraphcontext/cli/setup_macos.py +93 -0
  10. codegraphcontext/cli/setup_wizard.py +991 -0
  11. codegraphcontext/cli/visualizer.py +50 -0
  12. codegraphcontext/core/__init__.py +165 -0
  13. codegraphcontext/core/bundle_registry.py +181 -0
  14. codegraphcontext/core/cgc_bundle.py +858 -0
  15. codegraphcontext/core/cgcignore.py +118 -0
  16. codegraphcontext/core/database.py +273 -0
  17. codegraphcontext/core/database_falkordb.py +480 -0
  18. codegraphcontext/core/database_falkordb_remote.py +199 -0
  19. codegraphcontext/core/database_kuzu.py +636 -0
  20. codegraphcontext/core/falkor_worker.py +132 -0
  21. codegraphcontext/core/jobs.py +132 -0
  22. codegraphcontext/core/watcher.py +260 -0
  23. codegraphcontext/prompts.py +125 -0
  24. codegraphcontext/server.py +471 -0
  25. codegraphcontext/tool_definitions.py +220 -0
  26. codegraphcontext/tools/__init__.py +0 -0
  27. codegraphcontext/tools/advanced_language_query_tool.py +103 -0
  28. codegraphcontext/tools/code_finder.py +1160 -0
  29. codegraphcontext/tools/graph_builder.py +337 -0
  30. codegraphcontext/tools/handlers/analysis_handlers.py +114 -0
  31. codegraphcontext/tools/handlers/indexing_handlers.py +116 -0
  32. codegraphcontext/tools/handlers/management_handlers.py +339 -0
  33. codegraphcontext/tools/handlers/query_handlers.py +83 -0
  34. codegraphcontext/tools/handlers/watcher_handlers.py +83 -0
  35. codegraphcontext/tools/indexing/__init__.py +1 -0
  36. codegraphcontext/tools/indexing/constants.py +25 -0
  37. codegraphcontext/tools/indexing/discovery.py +64 -0
  38. codegraphcontext/tools/indexing/engine.py +109 -0
  39. codegraphcontext/tools/indexing/persistence/__init__.py +3 -0
  40. codegraphcontext/tools/indexing/persistence/parquet_loader.py +643 -0
  41. codegraphcontext/tools/indexing/persistence/writer.py +1114 -0
  42. codegraphcontext/tools/indexing/pipeline.py +171 -0
  43. codegraphcontext/tools/indexing/pre_scan.py +105 -0
  44. codegraphcontext/tools/indexing/resolution/__init__.py +8 -0
  45. codegraphcontext/tools/indexing/resolution/calls.py +253 -0
  46. codegraphcontext/tools/indexing/resolution/inheritance.py +91 -0
  47. codegraphcontext/tools/indexing/sanitize.py +41 -0
  48. codegraphcontext/tools/indexing/schema.py +79 -0
  49. codegraphcontext/tools/indexing/schema_contract.py +44 -0
  50. codegraphcontext/tools/indexing/scip_pipeline.py +140 -0
  51. codegraphcontext/tools/languages/c.py +563 -0
  52. codegraphcontext/tools/languages/cpp.py +616 -0
  53. codegraphcontext/tools/languages/csharp.py +551 -0
  54. codegraphcontext/tools/languages/dart.py +378 -0
  55. codegraphcontext/tools/languages/elixir.py +460 -0
  56. codegraphcontext/tools/languages/go.py +499 -0
  57. codegraphcontext/tools/languages/haskell.py +426 -0
  58. codegraphcontext/tools/languages/java.py +500 -0
  59. codegraphcontext/tools/languages/javascript.py +589 -0
  60. codegraphcontext/tools/languages/kotlin.py +640 -0
  61. codegraphcontext/tools/languages/perl.py +260 -0
  62. codegraphcontext/tools/languages/php.py +519 -0
  63. codegraphcontext/tools/languages/python.py +576 -0
  64. codegraphcontext/tools/languages/ruby.py +537 -0
  65. codegraphcontext/tools/languages/rust.py +295 -0
  66. codegraphcontext/tools/languages/scala.py +519 -0
  67. codegraphcontext/tools/languages/swift.py +491 -0
  68. codegraphcontext/tools/languages/typescript.py +575 -0
  69. codegraphcontext/tools/languages/typescriptjsx.py +151 -0
  70. codegraphcontext/tools/package_resolver.py +472 -0
  71. codegraphcontext/tools/query_tool_languages/c_toolkit.py +5 -0
  72. codegraphcontext/tools/query_tool_languages/cpp_toolkit.py +87 -0
  73. codegraphcontext/tools/query_tool_languages/csharp_toolkit.py +5 -0
  74. codegraphcontext/tools/query_tool_languages/dart_toolkit.py +5 -0
  75. codegraphcontext/tools/query_tool_languages/go_toolkit.py +5 -0
  76. codegraphcontext/tools/query_tool_languages/haskell_toolkit.py +5 -0
  77. codegraphcontext/tools/query_tool_languages/java_toolkit.py +5 -0
  78. codegraphcontext/tools/query_tool_languages/javascript_toolkit.py +5 -0
  79. codegraphcontext/tools/query_tool_languages/perl_toolkit.py +5 -0
  80. codegraphcontext/tools/query_tool_languages/python_toolkit.py +5 -0
  81. codegraphcontext/tools/query_tool_languages/ruby_toolkit.py +5 -0
  82. codegraphcontext/tools/query_tool_languages/rust_toolkit.py +5 -0
  83. codegraphcontext/tools/query_tool_languages/scala_toolkit.py +5 -0
  84. codegraphcontext/tools/query_tool_languages/swift_toolkit.py +5 -0
  85. codegraphcontext/tools/query_tool_languages/typescript_toolkit.py +5 -0
  86. codegraphcontext/tools/scip_indexer.py +467 -0
  87. codegraphcontext/tools/scip_pb2.py +2455 -0
  88. codegraphcontext/tools/system.py +133 -0
  89. codegraphcontext/tools/tree_sitter_parser.py +103 -0
  90. codegraphcontext/utils/debug_log.py +90 -0
  91. codegraphcontext/utils/path_ignore.py +55 -0
  92. codegraphcontext/utils/repo_path.py +27 -0
  93. codegraphcontext/utils/tree_sitter_manager.py +285 -0
  94. codegraphcontext/utils/visualize_graph.py +0 -0
  95. codegraphcontext/viz/server.py +282 -0
  96. codegraphcontext_rust-0.5.5.dist-info/METADATA +537 -0
  97. codegraphcontext_rust-0.5.5.dist-info/RECORD +101 -0
  98. codegraphcontext_rust-0.5.5.dist-info/WHEEL +4 -0
  99. codegraphcontext_rust-0.5.5.dist-info/entry_points.txt +3 -0
  100. codegraphcontext_rust-0.5.5.dist-info/licenses/LICENSE +21 -0
  101. 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