codegraphcontext-rust 0.5.5__cp311-cp311-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codegraphcontext/__init__.py +2 -0
- codegraphcontext/__main__.py +5 -0
- codegraphcontext/_cgc_rust.cp311-win_amd64.pyd +0 -0
- codegraphcontext/cli/__init__.py +1 -0
- codegraphcontext/cli/cli_helpers.py +774 -0
- codegraphcontext/cli/config_manager.py +1051 -0
- codegraphcontext/cli/main.py +2386 -0
- codegraphcontext/cli/registry_commands.py +404 -0
- codegraphcontext/cli/setup_macos.py +93 -0
- codegraphcontext/cli/setup_wizard.py +991 -0
- codegraphcontext/cli/visualizer.py +50 -0
- codegraphcontext/core/__init__.py +165 -0
- codegraphcontext/core/bundle_registry.py +181 -0
- codegraphcontext/core/cgc_bundle.py +858 -0
- codegraphcontext/core/cgcignore.py +118 -0
- codegraphcontext/core/database.py +273 -0
- codegraphcontext/core/database_falkordb.py +480 -0
- codegraphcontext/core/database_falkordb_remote.py +199 -0
- codegraphcontext/core/database_kuzu.py +636 -0
- codegraphcontext/core/falkor_worker.py +132 -0
- codegraphcontext/core/jobs.py +132 -0
- codegraphcontext/core/watcher.py +260 -0
- codegraphcontext/prompts.py +125 -0
- codegraphcontext/server.py +471 -0
- codegraphcontext/tool_definitions.py +220 -0
- codegraphcontext/tools/__init__.py +0 -0
- codegraphcontext/tools/advanced_language_query_tool.py +103 -0
- codegraphcontext/tools/code_finder.py +1160 -0
- codegraphcontext/tools/graph_builder.py +337 -0
- codegraphcontext/tools/handlers/analysis_handlers.py +114 -0
- codegraphcontext/tools/handlers/indexing_handlers.py +116 -0
- codegraphcontext/tools/handlers/management_handlers.py +339 -0
- codegraphcontext/tools/handlers/query_handlers.py +83 -0
- codegraphcontext/tools/handlers/watcher_handlers.py +83 -0
- codegraphcontext/tools/indexing/__init__.py +1 -0
- codegraphcontext/tools/indexing/constants.py +25 -0
- codegraphcontext/tools/indexing/discovery.py +64 -0
- codegraphcontext/tools/indexing/engine.py +109 -0
- codegraphcontext/tools/indexing/persistence/__init__.py +3 -0
- codegraphcontext/tools/indexing/persistence/parquet_loader.py +643 -0
- codegraphcontext/tools/indexing/persistence/writer.py +1114 -0
- codegraphcontext/tools/indexing/pipeline.py +171 -0
- codegraphcontext/tools/indexing/pre_scan.py +105 -0
- codegraphcontext/tools/indexing/resolution/__init__.py +8 -0
- codegraphcontext/tools/indexing/resolution/calls.py +253 -0
- codegraphcontext/tools/indexing/resolution/inheritance.py +91 -0
- codegraphcontext/tools/indexing/sanitize.py +41 -0
- codegraphcontext/tools/indexing/schema.py +79 -0
- codegraphcontext/tools/indexing/schema_contract.py +44 -0
- codegraphcontext/tools/indexing/scip_pipeline.py +140 -0
- codegraphcontext/tools/languages/c.py +563 -0
- codegraphcontext/tools/languages/cpp.py +616 -0
- codegraphcontext/tools/languages/csharp.py +551 -0
- codegraphcontext/tools/languages/dart.py +378 -0
- codegraphcontext/tools/languages/elixir.py +460 -0
- codegraphcontext/tools/languages/go.py +499 -0
- codegraphcontext/tools/languages/haskell.py +426 -0
- codegraphcontext/tools/languages/java.py +500 -0
- codegraphcontext/tools/languages/javascript.py +589 -0
- codegraphcontext/tools/languages/kotlin.py +640 -0
- codegraphcontext/tools/languages/perl.py +260 -0
- codegraphcontext/tools/languages/php.py +519 -0
- codegraphcontext/tools/languages/python.py +576 -0
- codegraphcontext/tools/languages/ruby.py +537 -0
- codegraphcontext/tools/languages/rust.py +295 -0
- codegraphcontext/tools/languages/scala.py +519 -0
- codegraphcontext/tools/languages/swift.py +491 -0
- codegraphcontext/tools/languages/typescript.py +575 -0
- codegraphcontext/tools/languages/typescriptjsx.py +151 -0
- codegraphcontext/tools/package_resolver.py +472 -0
- codegraphcontext/tools/query_tool_languages/c_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/cpp_toolkit.py +87 -0
- codegraphcontext/tools/query_tool_languages/csharp_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/dart_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/go_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/haskell_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/java_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/javascript_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/perl_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/python_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/ruby_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/rust_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/scala_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/swift_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/typescript_toolkit.py +5 -0
- codegraphcontext/tools/scip_indexer.py +467 -0
- codegraphcontext/tools/scip_pb2.py +2455 -0
- codegraphcontext/tools/system.py +133 -0
- codegraphcontext/tools/tree_sitter_parser.py +103 -0
- codegraphcontext/utils/debug_log.py +90 -0
- codegraphcontext/utils/path_ignore.py +55 -0
- codegraphcontext/utils/repo_path.py +27 -0
- codegraphcontext/utils/tree_sitter_manager.py +285 -0
- codegraphcontext/utils/visualize_graph.py +0 -0
- codegraphcontext/viz/server.py +282 -0
- codegraphcontext_rust-0.5.5.dist-info/METADATA +537 -0
- codegraphcontext_rust-0.5.5.dist-info/RECORD +101 -0
- codegraphcontext_rust-0.5.5.dist-info/WHEEL +4 -0
- codegraphcontext_rust-0.5.5.dist-info/entry_points.txt +3 -0
- codegraphcontext_rust-0.5.5.dist-info/licenses/LICENSE +21 -0
- codegraphcontext_rust-0.5.5.dist-info/sboms/cgc-python.cyclonedx.json +2333 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any, Dict, Optional, Tuple, 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
|