codegraphcontext-rust 0.5.5__cp310-cp310-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.cp310-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,589 @@
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
+ # --- Helpers to classify JS methods ---
8
+ _GETTER_RE = re.compile(r"^\s*(?:static\s+)?get\b")
9
+ _SETTER_RE = re.compile(r"^\s*(?:static\s+)?set\b")
10
+ _STATIC_RE = re.compile(r"^\s*static\b")
11
+
12
+
13
+ def _first_line_before_body(text: str) -> str:
14
+ """
15
+ Best-effort header extraction: take text before the first '{'
16
+ (covers class/object methods). Fallback to the first line.
17
+ """
18
+ head = text.split("{", 1)[0]
19
+ if not head.strip():
20
+ return text.splitlines()[0] if text.splitlines() else text
21
+ return head
22
+
23
+
24
+ def _classify_method_kind(header: str) -> Optional[str]:
25
+ """
26
+ Return 'getter' | 'setter' | 'static' | None.
27
+ Prefer 'getter'/'setter' over 'static' when both appear.
28
+ """
29
+ if _GETTER_RE.search(header):
30
+ return "getter"
31
+ if _SETTER_RE.search(header):
32
+ return "setter"
33
+ if _STATIC_RE.search(header):
34
+ return "static"
35
+ return None
36
+
37
+
38
+ JS_QUERIES = {
39
+ "functions": """
40
+ (function_declaration
41
+ name: (identifier) @name
42
+ parameters: (formal_parameters) @params
43
+ ) @function_node
44
+
45
+ (variable_declarator
46
+ name: (identifier) @name
47
+ value: (function_expression
48
+ parameters: (formal_parameters) @params
49
+ ) @function_node
50
+ )
51
+
52
+ (variable_declarator
53
+ name: (identifier) @name
54
+ value: (arrow_function
55
+ parameters: (formal_parameters) @params
56
+ ) @function_node
57
+ )
58
+
59
+ (variable_declarator
60
+ name: (identifier) @name
61
+ value: (arrow_function
62
+ parameter: (identifier) @single_param
63
+ ) @function_node
64
+ )
65
+
66
+ (method_definition
67
+ name: (property_identifier) @name
68
+ parameters: (formal_parameters) @params
69
+ ) @function_node
70
+
71
+ (assignment_expression
72
+ left: (member_expression
73
+ property: (property_identifier) @name
74
+ )
75
+ right: (function_expression
76
+ parameters: (formal_parameters) @params
77
+ ) @function_node
78
+ )
79
+
80
+ (assignment_expression
81
+ left: (member_expression
82
+ property: (property_identifier) @name
83
+ )
84
+ right: (arrow_function
85
+ parameters: (formal_parameters) @params
86
+ ) @function_node
87
+ )
88
+ """,
89
+ "classes": """
90
+ (class_declaration) @class
91
+ (class) @class
92
+ """,
93
+ "imports": """
94
+ (import_statement) @import
95
+ (call_expression
96
+ function: (identifier) @require_call (#eq? @require_call "require")
97
+ ) @import
98
+ """,
99
+ "calls": """
100
+ (call_expression function: (identifier) @name)
101
+ (call_expression function: (member_expression property: (property_identifier) @name))
102
+ (new_expression constructor: (identifier) @name)
103
+ (new_expression constructor: (member_expression property: (property_identifier) @name))
104
+ """,
105
+ "variables": """
106
+ (variable_declarator name: (identifier) @name)
107
+ """,
108
+ "docstrings": """
109
+ (comment) @docstring_comment
110
+ """,
111
+ }
112
+
113
+
114
+ class JavascriptTreeSitterParser:
115
+ """A JavaScript-specific parser using tree-sitter, encapsulating language-specific logic."""
116
+
117
+ def __init__(self, generic_parser_wrapper):
118
+ self.generic_parser_wrapper = generic_parser_wrapper
119
+ self.language_name = generic_parser_wrapper.language_name
120
+ self.language = generic_parser_wrapper.language
121
+ self.parser = generic_parser_wrapper.parser
122
+
123
+ def _get_node_text(self, node) -> str:
124
+ return node.text.decode('utf-8')
125
+
126
+ def _get_parent_context(self, node, types=('function_declaration', 'class_declaration', 'function_expression', 'method_definition', 'arrow_function')):
127
+ # JS specific context types
128
+ curr = node.parent
129
+ while curr:
130
+ if curr.type in types:
131
+ name_node = curr.child_by_field_name('name')
132
+ if not name_node and curr.type in ('function_expression', 'arrow_function'):
133
+ # Try to find name from variable declaration
134
+ if curr.parent and curr.parent.type == 'variable_declarator':
135
+ name_node = curr.parent.child_by_field_name('name')
136
+ elif curr.parent and curr.parent.type == 'assignment_expression':
137
+ name_node = curr.parent.child_by_field_name('left')
138
+ elif curr.parent and curr.parent.type == 'pair': # property: function
139
+ name_node = curr.parent.child_by_field_name('key')
140
+
141
+ return self._get_node_text(name_node) if name_node else None, curr.type, curr.start_point[0] + 1
142
+ curr = curr.parent
143
+ return None, None, None
144
+
145
+ def _calculate_complexity(self, node):
146
+ # JS specific complexity nodes
147
+ complexity_nodes = {
148
+ "if_statement", "for_statement", "while_statement", "do_statement",
149
+ "switch_statement", "case_statement", "conditional_expression",
150
+ "logical_expression", "binary_expression", "catch_clause"
151
+ }
152
+ count = 1
153
+
154
+ def traverse(n):
155
+ nonlocal count
156
+ if n.type in complexity_nodes:
157
+ count += 1
158
+ for child in n.children:
159
+ traverse(child)
160
+
161
+ traverse(node)
162
+ return count
163
+
164
+ def _get_docstring(self, body_node):
165
+ # JS specific docstring extraction (e.g., JSDoc comments)
166
+ # This is a placeholder and needs more sophisticated logic
167
+ return None
168
+
169
+ def parse(self, path: Path, is_dependency: bool = False, index_source: bool = False) -> Dict[str, Any]:
170
+ """Parses a file and returns its structure in a standardized dictionary format."""
171
+ self.index_source = index_source
172
+ with open(path, "r", encoding="utf-8") as f:
173
+ source_code = f.read()
174
+
175
+ tree = self.parser.parse(bytes(source_code, "utf8"))
176
+ root_node = tree.root_node
177
+
178
+ functions = self._find_functions(root_node)
179
+ classes = self._find_classes(root_node)
180
+ imports = self._find_imports(root_node)
181
+ function_calls = self._find_calls(root_node)
182
+ variables = self._find_variables(root_node)
183
+
184
+ return {
185
+ "path": str(path),
186
+ "functions": functions,
187
+ "classes": classes,
188
+ "variables": variables,
189
+ "imports": imports,
190
+ "function_calls": function_calls,
191
+ "is_dependency": is_dependency,
192
+ "lang": self.language_name,
193
+ }
194
+
195
+ def _find_functions(self, root_node):
196
+ functions = []
197
+ query_str = JS_QUERIES['functions']
198
+
199
+ # Local helpers so we don't depend on class attrs being present
200
+ def _fn_for_name(name_node):
201
+ current = name_node.parent
202
+ while current:
203
+ if current.type in ('function_declaration', 'function', 'arrow_function', 'method_definition', 'function_expression'):
204
+ return current
205
+ elif current.type in ('variable_declarator', 'assignment_expression'):
206
+ for child in current.children:
207
+ if child.type in ('function', 'arrow_function', 'function_expression'):
208
+ return child
209
+ current = current.parent
210
+ return None
211
+
212
+ def _fn_for_params(params_node):
213
+ current = params_node.parent
214
+ while current:
215
+ if current.type in ('function_declaration', 'function', 'arrow_function', 'method_definition', 'function_expression'):
216
+ return current
217
+ current = current.parent
218
+ return None
219
+ # stable keys so the same function only gets one bucket
220
+ def _key(n): # start/end byte + type is stable across captures
221
+ return (n.start_byte, n.end_byte, n.type)
222
+
223
+ # Collect captures grouped by function node
224
+ captures_by_function = {}
225
+ def _bucket_for(node):
226
+ fid = _key(node)
227
+ return captures_by_function.setdefault(fid, {
228
+ 'node': node, 'name': None, 'params': None, 'single_param': None
229
+ })
230
+
231
+ for node, capture_name in execute_query(self.language, query_str, root_node):
232
+ if capture_name == 'function_node':
233
+ _bucket_for(node)
234
+ elif capture_name == 'name':
235
+ fn = _fn_for_name(node)
236
+ if fn:
237
+ b = _bucket_for(fn)
238
+ b['name'] = self._get_node_text(node)
239
+ elif capture_name == 'params':
240
+ fn = _fn_for_params(node)
241
+ if fn:
242
+ b = _bucket_for(fn)
243
+ b['params'] = node
244
+ elif capture_name == 'single_param':
245
+ fn = _fn_for_params(node)
246
+ if fn:
247
+ b = _bucket_for(fn)
248
+ b['single_param'] = node
249
+
250
+ # Build Function entries
251
+ for _, data in captures_by_function.items():
252
+ func_node = data['node']
253
+
254
+ # Backfill name for method_definition if query didn't capture it
255
+ name = data.get('name')
256
+ if not name and func_node.type == 'method_definition':
257
+ nm = func_node.child_by_field_name('name')
258
+ if nm:
259
+ name = self._get_node_text(nm)
260
+ if not name:
261
+ continue # skip nameless functions
262
+
263
+ # Parameters
264
+ args = []
265
+ if data.get('params'):
266
+ args = self._extract_parameters(data['params'])
267
+ elif data.get('single_param'):
268
+ args = [self._get_node_text(data['single_param'])]
269
+
270
+ # Context & docstring
271
+ context, context_type, _ = self._get_parent_context(func_node)
272
+ class_context = context if context_type == 'class_declaration' else None
273
+ docstring = self._get_jsdoc_comment(func_node)
274
+
275
+ # Classify getter/setter/static (methods only)
276
+ js_kind = None
277
+ if func_node.type == 'method_definition':
278
+ header = _first_line_before_body(self._get_node_text(func_node))
279
+ js_kind = _classify_method_kind(header)
280
+
281
+ func_data = {
282
+ "name": name,
283
+ "line_number": func_node.start_point[0] + 1,
284
+ "end_line": func_node.end_point[0] + 1,
285
+ "args": args,
286
+ "lang": self.language_name,
287
+ "is_dependency": False,
288
+ }
289
+
290
+ if self.index_source:
291
+ func_data["source"] = self._get_node_text(func_node)
292
+ func_data["docstring"] = docstring
293
+ if js_kind is not None:
294
+ func_data["type"] = js_kind
295
+
296
+ functions.append(func_data)
297
+
298
+ return functions
299
+
300
+
301
+
302
+ def _find_function_node_for_name(self, name_node):
303
+ """Find the function node that contains this name node."""
304
+ current = name_node.parent
305
+ while current:
306
+ if current.type in ('function_declaration', 'function', 'arrow_function', 'method_definition'):
307
+ return current
308
+ elif current.type in ('variable_declarator', 'assignment_expression'):
309
+ # Check if this declarator/assignment contains a function
310
+ for child in current.children:
311
+ if child.type in ('function', 'arrow_function'):
312
+ return child
313
+ current = current.parent
314
+ return None
315
+
316
+
317
+ def _find_function_node_for_params(self, params_node):
318
+ """Find the function node that contains this parameters node."""
319
+ current = params_node.parent
320
+ while current:
321
+ if current.type in ('function_declaration', 'function', 'arrow_function', 'method_definition'):
322
+ return current
323
+ current = current.parent
324
+
325
+ return None
326
+
327
+
328
+ def _extract_parameters(self, params_node):
329
+ """Extract parameter names from formal_parameters node."""
330
+ params = []
331
+ if params_node.type == 'formal_parameters':
332
+ for child in params_node.children:
333
+ if child.type == 'identifier':
334
+ params.append(self._get_node_text(child))
335
+ elif child.type == 'assignment_pattern':
336
+ # Default parameter: param = defaultValue
337
+ left_child = child.child_by_field_name('left')
338
+ if left_child and left_child.type == 'identifier':
339
+ params.append(self._get_node_text(left_child))
340
+ elif child.type == 'rest_pattern':
341
+ # Rest parameter: ...args
342
+ argument = child.child_by_field_name('argument')
343
+ if argument and argument.type == 'identifier':
344
+ params.append(f"...{self._get_node_text(argument)}")
345
+ return params
346
+
347
+
348
+ def _get_jsdoc_comment(self, func_node):
349
+ """Extract JSDoc comment preceding the function."""
350
+ # Look for comments before the function
351
+ prev_sibling = func_node.prev_sibling
352
+ while prev_sibling and prev_sibling.type in ('comment', '\n', ' '):
353
+ if prev_sibling.type == 'comment':
354
+ comment_text = self._get_node_text(prev_sibling)
355
+ if comment_text.startswith('/**') and comment_text.endswith('*/'):
356
+ return comment_text.strip()
357
+ prev_sibling = prev_sibling.prev_sibling
358
+ return None
359
+
360
+
361
+ def _find_classes(self, root_node):
362
+ classes = []
363
+ query_str = JS_QUERIES['classes']
364
+ for class_node, capture_name in execute_query(self.language, query_str, root_node):
365
+ if capture_name == 'class':
366
+ name_node = class_node.child_by_field_name('name')
367
+ if not name_node: continue
368
+ name = self._get_node_text(name_node)
369
+
370
+ bases = []
371
+ heritage_node = next((child for child in class_node.children if child.type == 'class_heritage'), None)
372
+ if heritage_node:
373
+ if heritage_node.named_child_count > 0:
374
+ base_expr_node = heritage_node.named_child(0)
375
+ bases.append(self._get_node_text(base_expr_node))
376
+ elif heritage_node.child_count > 0:
377
+ # Fallback for anonymous nodes
378
+ base_expr_node = heritage_node.child(heritage_node.child_count - 1)
379
+ bases.append(self._get_node_text(base_expr_node))
380
+
381
+ class_data = {
382
+ "name": name,
383
+ "line_number": class_node.start_point[0] + 1,
384
+ "end_line": class_node.end_point[0] + 1,
385
+ "bases": bases,
386
+ "context": None,
387
+ "decorators": [],
388
+ "lang": self.language_name,
389
+ "is_dependency": False,
390
+ }
391
+
392
+ if self.index_source:
393
+ class_data["source"] = self._get_node_text(class_node)
394
+ class_data["docstring"] = self._get_docstring(class_node)
395
+
396
+ classes.append(class_data)
397
+ return classes
398
+
399
+
400
+ def _find_imports(self, root_node):
401
+ imports = []
402
+ query_str = JS_QUERIES['imports']
403
+ for node, capture_name in execute_query(self.language, query_str, root_node):
404
+ if capture_name != 'import':
405
+ continue
406
+
407
+ line_number = node.start_point[0] + 1
408
+
409
+ if node.type == 'import_statement':
410
+ source = self._get_node_text(node.child_by_field_name('source')).strip('\'"')
411
+
412
+ # Look for different import structures
413
+ import_clause = node.child_by_field_name('import')
414
+ if not import_clause:
415
+ imports.append({'name': source, 'source': source, 'alias': None, 'line_number': line_number,
416
+ 'lang': self.language_name})
417
+ continue
418
+
419
+ # Default import: import defaultExport from '...'
420
+ if import_clause.type == 'identifier':
421
+ alias = self._get_node_text(import_clause)
422
+ imports.append({'name': 'default', 'source': source, 'alias': alias, 'line_number': line_number,
423
+ 'lang': self.language_name})
424
+
425
+ # Namespace import: import * as name from '...'
426
+ elif import_clause.type == 'namespace_import':
427
+ alias_node = import_clause.child_by_field_name('alias')
428
+ if alias_node:
429
+ alias = self._get_node_text(alias_node)
430
+ imports.append({'name': '*', 'source': source, 'alias': alias, 'line_number': line_number,
431
+ 'lang': self.language_name})
432
+
433
+ # Named imports: import { name, name as alias } from '...'
434
+ elif import_clause.type == 'named_imports':
435
+ for specifier in import_clause.children:
436
+ if specifier.type == 'import_specifier':
437
+ name_node = specifier.child_by_field_name('name')
438
+ alias_node = specifier.child_by_field_name('alias')
439
+ original_name = self._get_node_text(name_node)
440
+ alias = self._get_node_text(alias_node) if alias_node else None
441
+ imports.append(
442
+ {'name': original_name, 'source': source, 'alias': alias, 'line_number': line_number,
443
+ 'lang': self.language_name})
444
+
445
+ elif node.type == 'call_expression': # require('...')
446
+ args = node.child_by_field_name('arguments')
447
+ if not args or args.named_child_count == 0: continue
448
+ source_node = args.named_child(0)
449
+ if not source_node or source_node.type != 'string': continue
450
+ source = self._get_node_text(source_node).strip('\'"')
451
+
452
+ alias = None
453
+ if node.parent.type == 'variable_declarator':
454
+ alias_node = node.parent.child_by_field_name('name')
455
+ if alias_node:
456
+ alias = self._get_node_text(alias_node)
457
+ imports.append({'name': source, 'source': source, 'alias': alias, 'line_number': line_number,
458
+ 'lang': self.language_name})
459
+
460
+ return imports
461
+
462
+
463
+ def _find_calls(self, root_node):
464
+ calls = []
465
+ query_str = JS_QUERIES['calls']
466
+ for node, capture_name in execute_query(self.language, query_str, root_node):
467
+ # Placeholder for JS call extraction logic
468
+ if capture_name == 'name':
469
+ # Traverse up to find the call_expression
470
+ call_node = node.parent
471
+ while call_node and call_node.type != 'call_expression' and call_node.type != 'program':
472
+ call_node = call_node.parent
473
+
474
+ name = self._get_node_text(node)
475
+
476
+ # Improved args extraction
477
+ args = []
478
+ arguments_node = None
479
+ if call_node and call_node.type in ('call_expression', 'new_expression'):
480
+ arguments_node = call_node.child_by_field_name('arguments')
481
+
482
+ if arguments_node:
483
+ for arg in arguments_node.children:
484
+ if arg.type not in ('(', ')', ','):
485
+ args.append(self._get_node_text(arg))
486
+
487
+ call_data = {
488
+ "name": name,
489
+ "full_name": self._get_node_text(call_node),
490
+ "line_number": node.start_point[0] + 1,
491
+ "args": args,
492
+ "inferred_obj_type": None,
493
+ "context": self._get_parent_context(node),
494
+ "class_context": self._get_parent_context(node, types=('class_declaration',))[:2],
495
+ "lang": self.language_name,
496
+ "is_dependency": False,
497
+ }
498
+ calls.append(call_data)
499
+ return calls
500
+
501
+
502
+ def _find_variables(self, root_node):
503
+ variables = []
504
+ query_str = JS_QUERIES['variables']
505
+ for match in execute_query(self.language, query_str, root_node):
506
+ capture_name = match[1]
507
+ node = match[0]
508
+
509
+ if capture_name == 'name':
510
+ var_node = node.parent
511
+ name = self._get_node_text(node)
512
+ value = None
513
+ type_text = None
514
+
515
+ # Detect if variable assigned to a function
516
+ value_node = var_node.child_by_field_name("value") if var_node else None
517
+
518
+ if value_node:
519
+ value_type = value_node.type
520
+
521
+ # --- Skip variables that are assigned a function ---
522
+ if value_type in ("function_expression", "arrow_function"):
523
+ continue
524
+
525
+ # Some grammars might have async_arrow_function or similar
526
+ if "function" in value_type or "arrow" in value_type:
527
+ continue
528
+
529
+ # --- Handle various assignment types ---
530
+ if value_type == "call_expression":
531
+ func_node = value_node.child_by_field_name("function")
532
+ value = self._get_node_text(func_node) if func_node else name
533
+ else:
534
+ value = self._get_node_text(value_node)
535
+
536
+ context, context_type, context_line = self._get_parent_context(node)
537
+ class_context = context if context_type == 'class_declaration' else None
538
+
539
+ variable_data = {
540
+ "name": name,
541
+ "line_number": node.start_point[0] + 1,
542
+ "value": value,
543
+ "type": type_text,
544
+ "context": context,
545
+ "class_context": class_context,
546
+ "lang": self.language_name,
547
+ "is_dependency": False,
548
+ }
549
+ variables.append(variable_data)
550
+ return variables
551
+
552
+
553
+ def pre_scan_javascript(files: list[Path], parser_wrapper) -> dict:
554
+ """Scans JavaScript files to create a map of class/function names to their file paths."""
555
+ imports_map = {}
556
+ query_str = """
557
+ (class_declaration name: (identifier) @name)
558
+ (function_declaration name: (identifier) @name)
559
+ (variable_declarator name: (identifier) @name value: (function_expression))
560
+ (variable_declarator name: (identifier) @name value: (arrow_function))
561
+ (method_definition name: (property_identifier) @name)
562
+ (assignment_expression
563
+ left: (member_expression
564
+ property: (property_identifier) @name
565
+ )
566
+ right: (function_expression)
567
+ )
568
+ (assignment_expression
569
+ left: (member_expression
570
+ property: (property_identifier) @name
571
+ )
572
+ right: (arrow_function)
573
+ )
574
+ """
575
+
576
+
577
+ for path in files:
578
+ try:
579
+ with open(path, "r", encoding="utf-8") as f:
580
+ tree = parser_wrapper.parser.parse(bytes(f.read(), "utf8"))
581
+
582
+ for capture, _ in execute_query(parser_wrapper.language, query_str, tree.root_node):
583
+ name = capture.text.decode('utf-8')
584
+ if name not in imports_map:
585
+ imports_map[name] = []
586
+ imports_map[name].append(str(path.resolve()))
587
+ except Exception as e:
588
+ warning_logger(f"Tree-sitter pre-scan failed for {path}: {e}")
589
+ return imports_map