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, List
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
+ SCALA_QUERIES = {
8
+ "functions": """
9
+ (function_definition
10
+ name: (identifier) @name
11
+ parameters: (parameters) @params
12
+ ) @function_node
13
+ """,
14
+ "classes": """
15
+ [
16
+ (class_definition name: (identifier) @name)
17
+ (object_definition name: (identifier) @name)
18
+ (trait_definition name: (identifier) @name)
19
+ ] @class
20
+ """,
21
+ "imports": """
22
+ (import_declaration) @import
23
+ """,
24
+ "calls": """
25
+ (call_expression) @call_node
26
+ (generic_function
27
+ function: (identifier) @name
28
+ ) @call_node
29
+ """,
30
+ "variables": """
31
+ (val_definition
32
+ pattern: (identifier) @name
33
+ ) @variable
34
+
35
+ (var_definition
36
+ pattern: (identifier) @name
37
+ ) @variable
38
+ """,
39
+ }
40
+
41
+ class ScalaTreeSitterParser:
42
+ def __init__(self, generic_parser_wrapper: Any):
43
+ self.generic_parser_wrapper = generic_parser_wrapper
44
+ self.language_name = "scala"
45
+ self.language = generic_parser_wrapper.language
46
+ self.parser = generic_parser_wrapper.parser
47
+
48
+ def parse(self, path: Path, is_dependency: bool = False, index_source: bool = False) -> Dict[str, Any]:
49
+ try:
50
+ self.index_source = index_source
51
+ with open(path, 'r', encoding='utf-8', errors='ignore') as f:
52
+ source_code = f.read()
53
+
54
+ if not source_code.strip():
55
+ warning_logger(f"Empty or whitespace-only file: {path}")
56
+ return {
57
+ "path": str(path),
58
+ "functions": [],
59
+ "classes": [],
60
+ "variables": [],
61
+ "imports": [],
62
+ "function_calls": [],
63
+ "is_dependency": is_dependency,
64
+ "lang": self.language_name,
65
+ }
66
+
67
+ tree = self.parser.parse(bytes(source_code, "utf8"))
68
+
69
+ parsed_functions = []
70
+ parsed_classes = []
71
+ parsed_variables = []
72
+ parsed_imports = []
73
+ parsed_calls = []
74
+
75
+ # Parse variables first for inference
76
+ if "variables" in SCALA_QUERIES:
77
+ try:
78
+ results = execute_query(self.language, SCALA_QUERIES["variables"], tree.root_node)
79
+ parsed_variables.extend(self._parse_variables(results, source_code, path))
80
+ except Exception as e:
81
+ error_logger(f"Error parsing Scala variables in {path}: {e}")
82
+
83
+ for capture_name, query in SCALA_QUERIES.items():
84
+ if capture_name == "variables": continue
85
+
86
+ try:
87
+ results = execute_query(self.language, query, tree.root_node)
88
+
89
+ if capture_name == "functions":
90
+ parsed_functions.extend(self._parse_functions(results, source_code, path))
91
+ elif capture_name == "classes":
92
+ parsed_classes.extend(self._parse_classes(results, source_code, path))
93
+ elif capture_name == "imports":
94
+ parsed_imports.extend(self._parse_imports(results, source_code))
95
+ elif capture_name == "calls":
96
+ parsed_calls.extend(self._parse_calls(results, source_code, path, parsed_variables))
97
+ except Exception as e:
98
+ # Some queries might fail if the grammar differs slightly, catch and log
99
+ error_logger(f"Error executing Scala query '{capture_name}' in {path}: {e}")
100
+
101
+ # Separate classes, traits, objects
102
+ final_classes = []
103
+ final_traits = []
104
+
105
+ for item in parsed_classes:
106
+ item_type = item.get('type', 'class')
107
+ if item_type == 'trait':
108
+ final_traits.append(item)
109
+ elif item_type == 'object':
110
+ item['is_object'] = True
111
+ final_classes.append(item)
112
+ else:
113
+ final_classes.append(item)
114
+
115
+ return {
116
+ "path": str(path),
117
+ "functions": parsed_functions,
118
+ "classes": final_classes,
119
+ "traits": final_traits,
120
+ "variables": parsed_variables,
121
+ "imports": parsed_imports,
122
+ "function_calls": parsed_calls,
123
+ "is_dependency": is_dependency,
124
+ "lang": self.language_name,
125
+ }
126
+
127
+ except Exception as e:
128
+ error_logger(f"Error parsing Scala file {path}: {e}")
129
+ return {
130
+ "path": str(path),
131
+ "functions": [],
132
+ "classes": [],
133
+ "variables": [],
134
+ "imports": [],
135
+ "function_calls": [],
136
+ "is_dependency": is_dependency,
137
+ "lang": self.language_name,
138
+ }
139
+
140
+ def _get_parent_context(self, node: Any) -> Tuple[Optional[str], Optional[str], Optional[int]]:
141
+ curr = node.parent
142
+ while curr:
143
+ if curr.type == "function_definition":
144
+ name_node = curr.child_by_field_name("name")
145
+ return (
146
+ self._get_node_text(name_node) if name_node else None,
147
+ curr.type,
148
+ curr.start_point[0] + 1,
149
+ )
150
+ if curr.type in ("class_definition", "object_definition", "trait_definition"):
151
+ name_node = curr.child_by_field_name("name")
152
+ return (
153
+ self._get_node_text(name_node) if name_node else None,
154
+ curr.type,
155
+ curr.start_point[0] + 1,
156
+ )
157
+ curr = curr.parent
158
+ return None, None, None
159
+
160
+ def _get_node_text(self, node: Any) -> str:
161
+ if not node: return ""
162
+ return node.text.decode("utf-8")
163
+
164
+ def _parse_functions(self, captures: list, source_code: str, path: Path) -> List[Dict[str, Any]]:
165
+ functions = []
166
+ seen_nodes = set()
167
+
168
+ for node, capture_name in captures:
169
+ if capture_name == "function_node":
170
+ node_id = (node.start_byte, node.end_byte, node.type)
171
+ if node_id in seen_nodes:
172
+ continue
173
+ seen_nodes.add(node_id)
174
+
175
+ try:
176
+ start_line = node.start_point[0] + 1
177
+ end_line = node.end_point[0] + 1
178
+
179
+ name_node = node.child_by_field_name("name")
180
+ if name_node:
181
+ func_name = self._get_node_text(name_node)
182
+
183
+ params_node = node.child_by_field_name("parameters")
184
+ parameters = []
185
+ if params_node:
186
+ params_text = self._get_node_text(params_node)
187
+ parameters = self._extract_parameter_names(params_text)
188
+
189
+ source_text = self._get_node_text(node)
190
+
191
+ context_name, context_type, context_line = self._get_parent_context(node)
192
+
193
+ func_data = {
194
+ "name": func_name,
195
+ "parameters": parameters,
196
+ "args": parameters, # 'args' is sometimes used instead of 'parameters'
197
+ "line_number": start_line,
198
+ "end_line": end_line,
199
+ "path": str(path),
200
+ "lang": self.language_name,
201
+ "context": context_name,
202
+ "class_context": context_name if context_type and ("class" in str(context_type) or "object" in str(context_type) or "trait" in str(context_type)) else None
203
+ }
204
+
205
+ if self.index_source:
206
+ func_data["source"] = source_text
207
+
208
+ functions.append(func_data)
209
+
210
+ except Exception as e:
211
+ error_logger(f"Error parsing function in {path}: {e}")
212
+ continue
213
+
214
+ return functions
215
+
216
+ def _parse_classes(self, captures: list, source_code: str, path: Path) -> List[Dict[str, Any]]:
217
+ classes = []
218
+ seen_nodes = set()
219
+
220
+ for node, capture_name in captures:
221
+ if capture_name == "class":
222
+ node_id = (node.start_byte, node.end_byte, node.type)
223
+ if node_id in seen_nodes:
224
+ continue
225
+ seen_nodes.add(node_id)
226
+
227
+ try:
228
+ start_line = node.start_point[0] + 1
229
+ end_line = node.end_point[0] + 1
230
+
231
+ name_node = node.child_by_field_name("name")
232
+ if name_node:
233
+ class_name = self._get_node_text(name_node)
234
+ source_text = self._get_node_text(node)
235
+
236
+ bases = []
237
+ # Look for extends clause (extends_clause)
238
+ # class_definition -> extends_clause -> template_body
239
+ extends_clause = None
240
+ for child in node.children:
241
+ if child.type == "extends_clause": # Might vary by grammar version: 'extends' keyword + types
242
+ extends_clause = child
243
+ break
244
+
245
+ if extends_clause:
246
+ for child in extends_clause.children:
247
+ if child.type == "type_identifier" or child.type == "user_type": # specific to scala grammar
248
+ bases.append(self._get_node_text(child))
249
+ elif child.type == "template_invocation":
250
+ # template_invocation -> user_type
251
+ pass
252
+
253
+ # Note: parsing bases in Scala can be complex (mixins with 'with' keyword).
254
+ # Using text based regex backup might be safer for now if tree query is hard.
255
+
256
+ class_data = {
257
+ "name": class_name,
258
+ "line_number": start_line,
259
+ "end_line": end_line,
260
+ "bases": bases,
261
+ "path": str(path),
262
+ "lang": self.language_name,
263
+ "type": node.type.replace("_definition", "") # class, object, trait
264
+ }
265
+
266
+ if self.index_source:
267
+ class_data["source"] = source_text
268
+
269
+ classes.append(class_data)
270
+
271
+ except Exception as e:
272
+ error_logger(f"Error parsing class in {path}: {e}")
273
+ continue
274
+
275
+ return classes
276
+
277
+ def _parse_variables(self, captures: list, source_code: str, path: Path) -> List[Dict[str, Any]]:
278
+ variables = []
279
+ seen_vars = set()
280
+
281
+ for node, capture_name in captures:
282
+ if capture_name == "variable":
283
+ # The capture is on the whole definition (val/var_definition)
284
+ # But we have @name on the identifier inside pattern.
285
+ pass
286
+ if capture_name == "name":
287
+ # Check parent context
288
+ if node.parent.type in ("val_definition", "var_definition"):
289
+ definition = node.parent
290
+ var_name = self._get_node_text(node)
291
+ start_line = node.start_point[0] + 1
292
+
293
+ start_byte = node.start_byte
294
+ if start_byte in seen_vars:
295
+ continue
296
+ seen_vars.add(start_byte)
297
+
298
+ ctx_name, ctx_type, ctx_line = self._get_parent_context(node)
299
+
300
+ # Type extraction: look for type_identifier in definition
301
+ var_type = "Unknown"
302
+ type_node = definition.child_by_field_name("type")
303
+ if type_node:
304
+ var_type = self._get_node_text(type_node)
305
+ else:
306
+ # Attempt inference from value
307
+ val_node = definition.child_by_field_name("value")
308
+ if val_node:
309
+ if val_node.type == "instance_expression" or val_node.type == "new_expression":
310
+ # new Calculator()
311
+ # instance_expression -> new, type_identifier, arguments
312
+ for child in val_node.children:
313
+ if child.type in ("type_identifier", "simple_type", "user_type", "generic_type"):
314
+ var_type = self._get_node_text(child)
315
+ break
316
+ elif child.type == "template_call": # sometimes nested
317
+ for sub in child.children:
318
+ if sub.type in ("type_identifier", "simple_type", "user_type"):
319
+ var_type = self._get_node_text(sub)
320
+ break
321
+ elif val_node.type == "call_expression":
322
+ # Circle(5.0)
323
+ # wrapper -> function(identifier)
324
+ func = val_node.child_by_field_name("function")
325
+ if func:
326
+ var_type = self._get_node_text(func)
327
+
328
+ variables.append({
329
+ "name": var_name,
330
+ "type": var_type,
331
+ "line_number": start_line,
332
+ "path": str(path),
333
+ "lang": self.language_name,
334
+ "context": ctx_name,
335
+ "class_context": ctx_name if ctx_type and ("class" in str(ctx_type) or "object" in str(ctx_type)) else None
336
+ })
337
+
338
+ return variables
339
+
340
+ def _parse_imports(self, captures: list, source_code: str) -> List[dict]:
341
+ imports = []
342
+
343
+ for node, capture_name in captures:
344
+ if capture_name == "import":
345
+ try:
346
+ # Scala imports can be complex: import java.util.{Date, List} or import java.util._
347
+ # We will try to extract the base path.
348
+ import_text = self._get_node_text(node)
349
+ # Simple heuristic: remove 'import ' and handle one level
350
+ clean_text = import_text.replace("import ", "").strip()
351
+
352
+ # Split logic for multiple imports in one line not handled perfectly here yet
353
+ # Just storing the whole text as name for now is better than crashing
354
+
355
+ path = clean_text
356
+
357
+ imports.append({
358
+ "name": path,
359
+ "full_import_name": path,
360
+ "line_number": node.start_point[0] + 1,
361
+ "alias": None,
362
+ "context": (None, None),
363
+ "lang": self.language_name,
364
+ "is_dependency": False,
365
+ })
366
+ except Exception as e:
367
+ error_logger(f"Error parsing import: {e}")
368
+ continue
369
+
370
+ return imports
371
+
372
+ def _parse_calls(self, captures: list, source_code: str, path: Path, variables: List[Dict] = []) -> List[Dict]:
373
+ calls = []
374
+ seen_calls = set()
375
+
376
+ for node, capture_name in captures:
377
+ if capture_name == "call_node":
378
+ try:
379
+ start_line = node.start_point[0] + 1
380
+
381
+ # Heuristic to find name
382
+ call_name = "unknown"
383
+ full_name = "unknown"
384
+
385
+ if node.type == "call_expression":
386
+ # function (child 0) arguments (child 1)
387
+ func_node = node.child_by_field_name("function")
388
+ if func_node:
389
+ if func_node.type == "field_expression": # obj.method
390
+ call_name = self._get_node_text(func_node.child_by_field_name("field")) # or name?
391
+ full_name = self._get_node_text(func_node)
392
+ elif func_node.type == "identifier":
393
+ call_name = self._get_node_text(func_node)
394
+ full_name = call_name
395
+ elif func_node.type == "generic_function":
396
+ # generic_function -> function
397
+ inner = func_node.child_by_field_name("function")
398
+ if inner:
399
+ full_name = self._get_node_text(inner)
400
+ call_name = full_name # simplified
401
+
402
+ if call_name == "unknown":
403
+ # Falback to text if simple
404
+ # call_name = self._get_node_text(node).split('(')[0]
405
+ continue
406
+
407
+ # Avoid duplicates
408
+ call_key = f"{call_name}_{start_line}"
409
+ if call_key in seen_calls:
410
+ continue
411
+ seen_calls.add(call_key)
412
+
413
+ ctx_name, ctx_type, ctx_line = self._get_parent_context(node)
414
+
415
+ # Inference from variables
416
+ inferred_type = None
417
+ if "." in full_name:
418
+ base_obj = full_name.split(".")[0]
419
+ # search for base_obj in variables
420
+ # Prefer variables in local context (ctx_name)
421
+
422
+ # Simple search: exact name match in same file
423
+ # We could improve by checking scope/context, but for now filtering by name is a good start
424
+ candidate = None
425
+ for v in variables:
426
+ if v["name"] == base_obj:
427
+ # Check if context matches or is strictly enclosing?
428
+ # For now, just take the first match or last match?
429
+ # Usually last match (closest definition)
430
+ candidate = v
431
+ if v["context"] == ctx_name:
432
+ break
433
+
434
+ if candidate:
435
+ inferred_type = candidate["type"]
436
+ elif call_name in variables: # Usually not happening as variables is list of dicts
437
+ pass
438
+
439
+ calls.append({
440
+ "name": call_name,
441
+ "full_name": full_name,
442
+ "line_number": start_line,
443
+ "args": [],
444
+ "inferred_obj_type": inferred_type,
445
+ "context": (ctx_name, ctx_type, ctx_line),
446
+ "class_context": (ctx_name, ctx_line) if ctx_type and ("class" in str(ctx_type) or "object" in str(ctx_type)) else (None, None),
447
+ "lang": self.language_name,
448
+ "is_dependency": False,
449
+ })
450
+ except Exception as e:
451
+ error_logger(f"Error parsing call: {e}")
452
+ continue
453
+
454
+ return calls
455
+
456
+
457
+ def _extract_parameter_names(self, params_text: str) -> List[str]:
458
+ # Simple extraction for Scala: (a: Int, b: String)
459
+ params = []
460
+ if not params_text: return params
461
+ clean = params_text.strip("()")
462
+ if not clean: return params
463
+
464
+ # Split by comma, respecting generics []
465
+ # Scala generics use []
466
+
467
+ # TODO: Reuse regex/parsing logic from other parsers or write simple one
468
+ # For now, simplistic split
469
+ parts = clean.split(',')
470
+ for p in parts:
471
+ # removing type: 'name: Type'
472
+ if ':' in p:
473
+ name = p.split(':')[0].strip()
474
+ # Remove modifiers like 'implicit', 'override', etc.
475
+ tokens = name.split()
476
+ if tokens:
477
+ params.append(tokens[-1])
478
+ else:
479
+ # maybe just name?
480
+ params.append(p.strip())
481
+ return params
482
+
483
+
484
+ def pre_scan_scala(files: list[Path], parser_wrapper) -> dict:
485
+ name_to_files = {}
486
+
487
+ for path in files:
488
+ try:
489
+ with open(path, 'r', encoding='utf-8', errors='ignore') as f:
490
+ content = f.read()
491
+
492
+ # package matches
493
+ package_name = ""
494
+ pkg_match = re.search(r'^\s*package\s+([\w\.]+)', content, re.MULTILINE)
495
+ if pkg_match:
496
+ package_name = pkg_match.group(1)
497
+
498
+ # class/object/trait matches
499
+ class_matches = re.finditer(r'\b(class|object|trait)\s+(\w+)', content)
500
+ for match in class_matches:
501
+ name = match.group(2)
502
+ type_ = match.group(1)
503
+
504
+ # Simple mapping
505
+ if name not in name_to_files:
506
+ name_to_files[name] = []
507
+ name_to_files[name].append(str(path))
508
+
509
+ # FQN mapping
510
+ if package_name:
511
+ fqn = f"{package_name}.{name}"
512
+ if fqn not in name_to_files:
513
+ name_to_files[fqn] = []
514
+ name_to_files[fqn].append(str(path))
515
+
516
+ except Exception as e:
517
+ error_logger(f"Error pre-scanning Scala file {path}: {e}")
518
+
519
+ return name_to_files