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,616 @@
1
+
2
+ from pathlib import Path
3
+ from typing import Any, Dict, Optional, Tuple
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
+ CPP_QUERIES = {
8
+ "functions": """
9
+ (function_definition
10
+ declarator: (function_declarator
11
+ declarator: [
12
+ (identifier) @name
13
+ (field_identifier) @name
14
+ (qualified_identifier) @qualified_name
15
+ ]
16
+ )
17
+ ) @function_node
18
+ """,
19
+ "classes": """
20
+ (class_specifier
21
+ name: (type_identifier) @name
22
+ ) @class
23
+ """,
24
+ "imports": """
25
+ (preproc_include
26
+ path: [
27
+ (string_literal) @path
28
+ (system_lib_string) @path
29
+ ]
30
+ ) @import
31
+ """,
32
+ "calls": """
33
+ (call_expression
34
+ function: [
35
+ (identifier) @function_name
36
+ (field_expression
37
+ field: (field_identifier) @method_name
38
+ )
39
+ (qualified_identifier) @scoped_name
40
+ ]
41
+ arguments: (argument_list) @args
42
+ )
43
+ """,
44
+ "enums":"""
45
+ (enum_specifier
46
+ name: (type_identifier) @name
47
+ body: (enumerator_list
48
+ (enumerator
49
+ name: (identifier) @value
50
+ )*
51
+ )? @body
52
+ ) @enum
53
+ """,
54
+ "structs":"""
55
+ (struct_specifier
56
+ name: (type_identifier) @name
57
+ body: (field_declaration_list)? @body
58
+ ) @struct
59
+ """,
60
+ "unions": """
61
+ (union_specifier
62
+ name: (type_identifier)? @name
63
+ body: (field_declaration_list
64
+ (field_declaration
65
+ declarator: [
66
+ (field_identifier) @value
67
+ (pointer_declarator (field_identifier) @value)
68
+ (array_declarator (field_identifier) @value)
69
+ ]
70
+ )*
71
+ )? @body
72
+ ) @union
73
+
74
+ """,
75
+ "macros": """
76
+ (preproc_def
77
+ name: (identifier) @name
78
+ ) @macro
79
+ """,
80
+ "variables": """
81
+ (declaration
82
+ declarator: (init_declarator
83
+ declarator: (identifier) @name))
84
+
85
+ (declaration
86
+ declarator: (init_declarator
87
+ declarator: (pointer_declarator
88
+ declarator: (identifier) @name)))
89
+
90
+ (field_declaration
91
+ declarator: [
92
+ (field_identifier) @name
93
+ (pointer_declarator declarator: (field_identifier) @name)
94
+ (array_declarator declarator: (field_identifier) @name)
95
+ (reference_declarator (field_identifier) @name)
96
+ ]
97
+ )
98
+ """,
99
+ "lambda_assignments": """
100
+ ; Match a lambda assigned to a variable
101
+ (declaration
102
+ declarator: (init_declarator
103
+ declarator: (identifier) @name
104
+ value: (lambda_expression) @lambda_node))
105
+ """
106
+
107
+ }
108
+
109
+ class CppTreeSitterParser:
110
+ """A C++-specific parser using tree-sitter."""
111
+
112
+ def __init__(self, generic_parser_wrapper):
113
+ self.generic_parser_wrapper = generic_parser_wrapper
114
+ self.language_name = "cpp"
115
+ self.language = generic_parser_wrapper.language
116
+ self.parser = generic_parser_wrapper.parser
117
+
118
+ def _get_node_text(self, node) -> str:
119
+ return node.text.decode('utf-8')
120
+
121
+ def parse(self, path: Path, is_dependency: bool = False, index_source: bool = False, **kwargs) -> Dict:
122
+ """Parses a C++ file and returns its structure."""
123
+ self.index_source = index_source
124
+ with open(path, "r", encoding="utf-8", errors="ignore") as f:
125
+ source_code = f.read()
126
+
127
+ tree = self.parser.parse(bytes(source_code, "utf8"))
128
+ root_node = tree.root_node
129
+
130
+ functions = self._find_functions(root_node)
131
+ functions.extend(self._find_lambda_assignments(root_node))
132
+ function_calls = self._find_calls(root_node)
133
+ classes = self._find_classes(root_node)
134
+ imports = self._find_imports(root_node)
135
+ structs = self._find_structs(root_node)
136
+ enums = self._find_enums(root_node)
137
+ unions = self._find_unions(root_node)
138
+ macros = self._find_macros(root_node)
139
+ variables = self._find_variables(root_node)
140
+
141
+ return {
142
+ "path": str(path),
143
+ "functions": functions,
144
+ "classes": classes,
145
+ "structs": structs,
146
+ "enums": enums,
147
+ "unions": unions,
148
+ "macros": macros,
149
+ "variables": variables,
150
+ "declarations": [], # Placeholder
151
+ "imports": imports,
152
+ "function_calls": function_calls,
153
+ "is_dependency": is_dependency,
154
+ "lang": self.language_name,
155
+ }
156
+
157
+ def _find_functions(self, root_node):
158
+ functions = []
159
+ query_str = CPP_QUERIES['functions']
160
+ for match in execute_query(self.language, query_str, root_node):
161
+ capture_name = match[1]
162
+ node = match[0]
163
+ if capture_name in ('name', 'qualified_name'):
164
+ # Find the enclosing function_definition node
165
+ func_node = node.parent
166
+ while func_node and func_node.type != 'function_definition':
167
+ func_node = func_node.parent
168
+
169
+ if not func_node: continue
170
+
171
+ # For qualified names like QueueElement::execute, extract
172
+ # both the class context and the method name
173
+ raw_text = self._get_node_text(node)
174
+ class_context = None
175
+ if capture_name == 'qualified_name' and '::' in raw_text:
176
+ parts = raw_text.rsplit('::', 1)
177
+ class_context = parts[0]
178
+ name = parts[1]
179
+ else:
180
+ name = raw_text
181
+
182
+ params = self._extract_function_params(func_node)
183
+
184
+ func_data = {
185
+ "name": name,
186
+ "line_number": node.start_point[0] + 1,
187
+ "end_line": func_node.end_point[0] + 1,
188
+ "args": params,
189
+ }
190
+
191
+ if class_context:
192
+ func_data["class_context"] = class_context
193
+
194
+ if self.index_source:
195
+ func_data["source"] = self._get_node_text(func_node)
196
+
197
+ functions.append(func_data)
198
+ return functions
199
+
200
+ def _extract_function_params(self, func_node) -> list[str]:
201
+ params = []
202
+ declarator_node = func_node.child_by_field_name('declarator')
203
+ if not declarator_node:
204
+ return []
205
+
206
+ parameters_node = declarator_node.child_by_field_name('parameters')
207
+ if not parameters_node or parameters_node.type != 'parameter_list':
208
+ return []
209
+
210
+ for param in parameters_node.children:
211
+ if param.type == 'parameter_declaration':
212
+ # Extract name
213
+ param_decl = param.child_by_field_name('declarator')
214
+ # Unwrap pointers/refs to find identifier
215
+ while param_decl and param_decl.type not in ('identifier', 'field_identifier', 'type_identifier'):
216
+ child = param_decl.child_by_field_name('declarator')
217
+ if child:
218
+ param_decl = child
219
+ else:
220
+ break
221
+
222
+ name = self._get_node_text(param_decl) if param_decl else ""
223
+
224
+ # Extract type
225
+ param_type_node = param.child_by_field_name('type')
226
+ type_str = self._get_node_text(param_type_node) if param_type_node else ""
227
+
228
+ if name:
229
+ # Storing "type name" string, or just name?
230
+ # Standard in this project seems to be list of strings.
231
+ # Given C++ complexity, providing "type name" provides more info.
232
+ if type_str:
233
+ params.append(f"{type_str} {name}")
234
+ else:
235
+ params.append(name)
236
+ return params
237
+
238
+ def _find_classes(self, root_node):
239
+ classes = []
240
+ query_str = CPP_QUERIES['classes']
241
+ for match in execute_query(self.language, query_str, root_node):
242
+ capture_name = match[1]
243
+ node = match[0]
244
+ if capture_name == 'name':
245
+ class_node = node.parent
246
+ name = self._get_node_text(node)
247
+ bases = self._extract_base_classes(class_node)
248
+ class_data = {
249
+ "name": name,
250
+ "line_number": node.start_point[0] + 1,
251
+ "end_line": class_node.end_point[0] + 1,
252
+ "bases": bases,
253
+ }
254
+ if self.index_source:
255
+ class_data["source"] = self._get_node_text(class_node)
256
+ classes.append(class_data)
257
+ return classes
258
+
259
+ def _extract_base_classes(self, class_node) -> list[str]:
260
+ """Extract base class names from a class_specifier node.
261
+
262
+ Handles: class Foo : public Bar, private Baz, virtual Base
263
+ Tree-sitter AST: class_specifier -> base_class_clause -> (base_class_specifier)+
264
+ Each base_class_specifier has an optional access_specifier and a type node.
265
+ """
266
+ bases = []
267
+ for child in class_node.children:
268
+ if child.type == 'base_class_clause':
269
+ for base_spec in child.children:
270
+ if base_spec.type in ('base_class_specifier', 'type_identifier',
271
+ 'qualified_identifier', 'template_type'):
272
+ # base_class_specifier contains access specifier + type
273
+ if base_spec.type == 'base_class_specifier':
274
+ # Find the type identifier within the base specifier
275
+ for sub in base_spec.children:
276
+ if sub.type in ('type_identifier', 'qualified_identifier',
277
+ 'template_type'):
278
+ base_name = self._get_node_text(sub)
279
+ # Strip template args for graph matching
280
+ if '<' in base_name:
281
+ base_name = base_name[:base_name.index('<')].strip()
282
+ bases.append(base_name)
283
+ break
284
+ else:
285
+ # Direct type node (no access specifier)
286
+ base_name = self._get_node_text(base_spec)
287
+ if '<' in base_name:
288
+ base_name = base_name[:base_name.index('<')].strip()
289
+ bases.append(base_name)
290
+ break # Only one base_class_clause per class
291
+ return bases
292
+
293
+ def _find_imports(self, root_node):
294
+ imports = []
295
+ query_str = CPP_QUERIES['imports']
296
+ for match in execute_query(self.language, query_str, root_node):
297
+ capture_name = match[1]
298
+ node = match[0]
299
+ if capture_name == 'path':
300
+ path = self._get_node_text(node).strip('<>')
301
+ imports.append({
302
+ "name": path,
303
+ "full_import_name": path,
304
+ "line_number": node.start_point[0] + 1,
305
+ "alias": None,
306
+ })
307
+ return imports
308
+
309
+ def _find_enums(self, root_node):
310
+ enums = []
311
+ query_str = CPP_QUERIES['enums']
312
+ for node, capture_name in execute_query(self.language, query_str, root_node):
313
+ if capture_name == 'name':
314
+ name = self._get_node_text(node)
315
+ enum_node = node.parent
316
+ enum_data = {
317
+ "name": name,
318
+ "line_number": node.start_point[0] + 1,
319
+ "end_line": enum_node.end_point[0] + 1,
320
+ }
321
+ if self.index_source:
322
+ enum_data["source"] = self._get_node_text(enum_node)
323
+ enums.append(enum_data)
324
+ return enums
325
+
326
+ def _find_structs(self, root_node):
327
+ structs = []
328
+ query_str = CPP_QUERIES['structs']
329
+ for node, capture_name in execute_query(self.language, query_str, root_node):
330
+ if capture_name == 'name':
331
+ name = self._get_node_text(node)
332
+ struct_node = node.parent
333
+ struct_data = {
334
+ "name": name,
335
+ "line_number": node.start_point[0] + 1,
336
+ "end_line": struct_node.end_point[0] + 1,
337
+ }
338
+ if self.index_source:
339
+ struct_data["source"] = self._get_node_text(struct_node)
340
+ structs.append(struct_data)
341
+ return structs
342
+
343
+ def _find_unions(self, root_node):
344
+ unions = []
345
+ query_str = CPP_QUERIES['unions']
346
+ for node, capture_name in execute_query(self.language, query_str, root_node):
347
+ if capture_name == 'name':
348
+ name = self._get_node_text(node)
349
+ union_node = node.parent
350
+ union_data = {
351
+ "name": name,
352
+ "line_number": node.start_point[0] + 1,
353
+ "end_line": union_node.end_point[0] + 1,
354
+ }
355
+ if self.index_source:
356
+ union_data["source"] = self._get_node_text(union_node)
357
+ unions.append(union_data)
358
+ return unions
359
+
360
+ def _find_macros(self, root_node):
361
+ macros = []
362
+ query_str = CPP_QUERIES['macros']
363
+ for match in execute_query(self.language, query_str, root_node):
364
+ capture_name = match[1]
365
+ node = match[0]
366
+ if capture_name == 'name':
367
+ macro_node = node.parent
368
+ name = self._get_node_text(node)
369
+ macro_data = {
370
+ "name": name,
371
+ "line_number": node.start_point[0] + 1,
372
+ "end_line": macro_node.end_point[0] + 1,
373
+ }
374
+ if self.index_source:
375
+ macro_data["source"] = self._get_node_text(macro_node)
376
+ macros.append(macro_data)
377
+ return macros
378
+
379
+ def _find_lambda_assignments(self, root_node):
380
+ functions = []
381
+ query_str = CPP_QUERIES.get('lambda_assignments')
382
+ if not query_str: return []
383
+
384
+ for match in execute_query(self.language, query_str, root_node):
385
+ capture_name = match[1]
386
+ node = match[0]
387
+
388
+ if capture_name == 'name':
389
+ assignment_node = node.parent
390
+ lambda_node = assignment_node.child_by_field_name('value')
391
+ if lambda_node is None or lambda_node.type != 'lambda_expression':
392
+ continue
393
+
394
+ params_node = lambda_node.child_by_field_name('declarator')
395
+ if params_node:
396
+ params_node = params_node.child_by_field_name('parameters')
397
+ name = self._get_node_text(node)
398
+ params_node = lambda_node.child_by_field_name('parameters')
399
+
400
+ context, context_type, _ = self._get_parent_context(assignment_node)
401
+ class_context, _, _ = self._get_parent_context(assignment_node, types=('class_specifier',))
402
+
403
+ func_data = {
404
+ "name": name,
405
+ "line_number": node.start_point[0] + 1,
406
+ "end_line": assignment_node.end_point[0] + 1,
407
+ "args": [p for p in [self._get_node_text(p) for p in params_node.children if p.type == 'identifier'] if p] if params_node else [],
408
+
409
+ "docstring": None,
410
+ "cyclomatic_complexity": 1,
411
+ "context": context,
412
+ "context_type": context_type,
413
+ "class_context": class_context,
414
+ "decorators": [],
415
+ "lang": self.language_name,
416
+ "is_dependency": False,
417
+ }
418
+
419
+ if self.index_source:
420
+ func_data["source"] = self._get_node_text(assignment_node)
421
+
422
+ functions.append(func_data)
423
+ return functions
424
+
425
+ def _find_variables(self, root_node):
426
+ variables = []
427
+ query_str = CPP_QUERIES['variables']
428
+ for match in execute_query(self.language, query_str, root_node):
429
+ capture_name = match[1]
430
+ node = match[0]
431
+
432
+ if capture_name == 'name':
433
+ assignment_node = node.parent
434
+
435
+ # Skip lambda assignments, they are handled by _find_lambda_assignments
436
+ right_node = assignment_node.child_by_field_name('value')
437
+ if right_node and right_node.type == 'lambda_expression':
438
+ continue
439
+
440
+ name = self._get_node_text(node)
441
+ value = self._get_node_text(right_node) if right_node else None
442
+
443
+ type_node = assignment_node.child_by_field_name('type')
444
+ type_text = self._get_node_text(type_node) if type_node else None
445
+
446
+ context, _, _ = self._get_parent_context(node)
447
+ class_context, _, _ = self._get_parent_context(node, types=('class_specifier',))
448
+
449
+ variable_data = {
450
+ "name": name,
451
+ "line_number": node.start_point[0] + 1,
452
+ "value": value,
453
+ "type": type_text,
454
+ "context": context,
455
+ "class_context": class_context,
456
+ "lang": self.language_name,
457
+ "is_dependency": False,
458
+ }
459
+ variables.append(variable_data)
460
+ return variables
461
+
462
+ def _get_parent_context(self, node, types=('function_definition', 'class_specifier')):
463
+ curr = node.parent
464
+ while curr:
465
+ if curr.type in types:
466
+ if curr.type == 'function_definition':
467
+ # Traverse declarator to find name, handling qualified names
468
+ decl = curr.child_by_field_name('declarator')
469
+ while decl:
470
+ if decl.type == 'identifier':
471
+ return self._get_node_text(decl), curr.type, decl.start_point[0] + 1
472
+ if decl.type == 'qualified_identifier':
473
+ # e.g. QueueElement::execute — return just the method name
474
+ text = self._get_node_text(decl)
475
+ name = text.rsplit('::', 1)[-1] if '::' in text else text
476
+ return name, curr.type, decl.start_point[0] + 1
477
+ if decl.type == 'field_identifier':
478
+ return self._get_node_text(decl), curr.type, decl.start_point[0] + 1
479
+
480
+ child = decl.child_by_field_name('declarator')
481
+ if child:
482
+ decl = child
483
+ else:
484
+ break
485
+ # Fallback or if not found
486
+ return None, curr.type, curr.start_point[0] + 1
487
+ elif curr.type == 'class_specifier':
488
+ name_node = curr.child_by_field_name('name')
489
+ return self._get_node_text(name_node) if name_node else None, curr.type, curr.start_point[0] + 1
490
+ else:
491
+ name_node = curr.child_by_field_name('name')
492
+ return self._get_node_text(name_node) if name_node else None, curr.type, curr.start_point[0] + 1
493
+ curr = curr.parent
494
+ return None, None, None
495
+
496
+ def _find_calls(self, root_node):
497
+ calls = []
498
+ query_str = CPP_QUERIES['calls']
499
+ for node, capture_name in execute_query(self.language, query_str, root_node):
500
+ if capture_name in ("function_name", "method_name", "scoped_name"):
501
+ raw_text = self._get_node_text(node)
502
+
503
+ # Determine the called function name and any object/class qualifier
504
+ inferred_obj_type = None
505
+ if capture_name == "scoped_name" and '::' in raw_text:
506
+ # e.g. QueueElement::setStatus or std::move
507
+ parts = raw_text.rsplit('::', 1)
508
+ func_name = parts[1]
509
+ inferred_obj_type = parts[0]
510
+ elif capture_name == "method_name":
511
+ # e.g. obj.method() or ptr->method() — field_expression
512
+ func_name = raw_text
513
+ # Try to get the object expression for type inference
514
+ field_expr = node.parent # field_expression node
515
+ if field_expr and field_expr.type == 'field_expression':
516
+ obj_node = field_expr.child_by_field_name('argument')
517
+ if obj_node:
518
+ obj_text = self._get_node_text(obj_node)
519
+ # Treat this->method like self.method for resolution
520
+ if obj_text == 'this':
521
+ inferred_obj_type = 'this'
522
+ else:
523
+ inferred_obj_type = obj_text
524
+ else:
525
+ func_name = raw_text
526
+
527
+ # Get context info (which function/class contains this call)
528
+ context_name, context_type, context_line = self._get_parent_context(node)
529
+ class_context, _, _ = self._get_parent_context(node, types=("class_specifier",))
530
+
531
+ call_data = {
532
+ "name": func_name,
533
+ "full_name": raw_text,
534
+ "line_number": node.start_point[0] + 1,
535
+ "args": [],
536
+ "inferred_obj_type": inferred_obj_type,
537
+ "context": (context_name, context_type, context_line),
538
+ "class_context": class_context,
539
+ "lang": self.language_name,
540
+ "is_dependency": False,
541
+ }
542
+ calls.append(call_data)
543
+ return calls
544
+
545
+ def _get_full_name(self, node):
546
+ "Builds a fully qualified name for a function or call node."
547
+
548
+ name_parts = []
549
+
550
+ # Move upward and collect parent scopes
551
+ curr = node
552
+ while curr:
553
+ if curr.type in ("function_definition", "function_declarator"):
554
+ id_node = curr.child_by_field_name("declarator")
555
+ if id_node:
556
+ if id_node.type == "identifier":
557
+ name_parts.insert(0, id_node.text.decode("utf8"))
558
+ elif id_node.type == "qualified_identifier":
559
+ # Already contains Class::method — use it directly
560
+ return id_node.text.decode("utf8")
561
+ elif curr.type == "class_specifier":
562
+ name_node = curr.child_by_field_name("name")
563
+ if name_node:
564
+ name_parts.insert(0, name_node.text.decode("utf8"))
565
+ elif curr.type == "namespace_definition":
566
+ name_node = curr.child_by_field_name("name")
567
+ if name_node:
568
+ name_parts.insert(0, name_node.text.decode("utf8"))
569
+ curr = curr.parent
570
+
571
+ return "::".join(name_parts) if name_parts else None
572
+
573
+
574
+ def pre_scan_cpp(files: list[Path], parser_wrapper) -> dict:
575
+ """
576
+ Quickly scans C++ files to build a map of top-level class, struct, and function names
577
+ to their file paths.
578
+ """
579
+ imports_map = {}
580
+
581
+ query_str = """
582
+ (class_specifier name: (type_identifier) @name)
583
+ (struct_specifier name: (type_identifier) @name)
584
+ (function_definition declarator: (function_declarator declarator: (identifier) @name))
585
+ (function_definition declarator: (function_declarator declarator: (qualified_identifier) @qualified_name))
586
+ """
587
+
588
+
589
+ for path in files:
590
+ try:
591
+ with open(path, "r", encoding="utf-8", errors="ignore") as f:
592
+ source_bytes = f.read().encode("utf-8")
593
+ tree = parser_wrapper.parser.parse(source_bytes)
594
+
595
+ for node, capture_name in execute_query(parser_wrapper.language, query_str, tree.root_node):
596
+ resolved_path = str(path.resolve())
597
+ if capture_name == "name":
598
+ name = node.text.decode("utf-8")
599
+ paths = imports_map.setdefault(name, [])
600
+ if resolved_path not in paths:
601
+ paths.append(resolved_path)
602
+ elif capture_name == "qualified_name":
603
+ # e.g. QueueElement::execute — index both the full name and the method name
604
+ full_name = node.text.decode("utf-8")
605
+ paths = imports_map.setdefault(full_name, [])
606
+ if resolved_path not in paths:
607
+ paths.append(resolved_path)
608
+ if '::' in full_name:
609
+ method_name = full_name.rsplit('::', 1)[1]
610
+ paths = imports_map.setdefault(method_name, [])
611
+ if resolved_path not in paths:
612
+ paths.append(resolved_path)
613
+ except Exception as e:
614
+ warning_logger(f"Tree-sitter pre-scan failed for {path}: {e}")
615
+
616
+ return imports_map