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,500 @@
|
|
|
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
|
+
JAVA_QUERIES = {
|
|
8
|
+
"functions": """
|
|
9
|
+
(method_declaration
|
|
10
|
+
name: (identifier) @name
|
|
11
|
+
parameters: (formal_parameters) @params
|
|
12
|
+
) @function_node
|
|
13
|
+
|
|
14
|
+
(constructor_declaration
|
|
15
|
+
name: (identifier) @name
|
|
16
|
+
parameters: (formal_parameters) @params
|
|
17
|
+
) @function_node
|
|
18
|
+
""",
|
|
19
|
+
"classes": """
|
|
20
|
+
[
|
|
21
|
+
(class_declaration name: (identifier) @name)
|
|
22
|
+
(interface_declaration name: (identifier) @name)
|
|
23
|
+
(enum_declaration name: (identifier) @name)
|
|
24
|
+
(annotation_type_declaration name: (identifier) @name)
|
|
25
|
+
] @class
|
|
26
|
+
""",
|
|
27
|
+
"imports": """
|
|
28
|
+
(import_declaration) @import
|
|
29
|
+
""",
|
|
30
|
+
# variables MUST be parsed before calls so we can build var_type_map
|
|
31
|
+
# and populate inferred_obj_type on method-call nodes for cross-file resolution.
|
|
32
|
+
"variables": """
|
|
33
|
+
(local_variable_declaration
|
|
34
|
+
type: (_) @type
|
|
35
|
+
declarator: (variable_declarator
|
|
36
|
+
name: (identifier) @name
|
|
37
|
+
)
|
|
38
|
+
) @variable
|
|
39
|
+
|
|
40
|
+
(field_declaration
|
|
41
|
+
type: (_) @type
|
|
42
|
+
declarator: (variable_declarator
|
|
43
|
+
name: (identifier) @name
|
|
44
|
+
)
|
|
45
|
+
) @variable
|
|
46
|
+
""",
|
|
47
|
+
"calls": """
|
|
48
|
+
(method_invocation
|
|
49
|
+
name: (identifier) @name
|
|
50
|
+
) @call_node
|
|
51
|
+
|
|
52
|
+
(object_creation_expression
|
|
53
|
+
type: [
|
|
54
|
+
(type_identifier)
|
|
55
|
+
(scoped_type_identifier)
|
|
56
|
+
(generic_type)
|
|
57
|
+
] @name
|
|
58
|
+
) @call_node
|
|
59
|
+
""",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
class JavaTreeSitterParser:
|
|
63
|
+
def __init__(self, generic_parser_wrapper: Any):
|
|
64
|
+
self.generic_parser_wrapper = generic_parser_wrapper
|
|
65
|
+
self.language_name = "java"
|
|
66
|
+
self.language = generic_parser_wrapper.language
|
|
67
|
+
self.parser = generic_parser_wrapper.parser
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def _strip_generic(type_str: str) -> str:
|
|
71
|
+
"""Return the raw type name without generic parameters, e.g. 'List<String>' -> 'List'."""
|
|
72
|
+
bracket = type_str.find('<')
|
|
73
|
+
return type_str[:bracket].strip() if bracket != -1 else type_str.strip()
|
|
74
|
+
|
|
75
|
+
def parse(self, path: Path, is_dependency: bool = False, index_source: bool = False) -> Dict[str, Any]:
|
|
76
|
+
try:
|
|
77
|
+
self.index_source = index_source
|
|
78
|
+
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
79
|
+
source_code = f.read()
|
|
80
|
+
|
|
81
|
+
if not source_code.strip():
|
|
82
|
+
warning_logger(f"Empty or whitespace-only file: {path}")
|
|
83
|
+
return {
|
|
84
|
+
"path": str(path),
|
|
85
|
+
"functions": [],
|
|
86
|
+
"classes": [],
|
|
87
|
+
"variables": [],
|
|
88
|
+
"imports": [],
|
|
89
|
+
"function_calls": [],
|
|
90
|
+
"is_dependency": is_dependency,
|
|
91
|
+
"lang": self.language_name,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
tree = self.parser.parse(bytes(source_code, "utf8"))
|
|
95
|
+
|
|
96
|
+
parsed_functions = []
|
|
97
|
+
parsed_classes = []
|
|
98
|
+
parsed_variables = []
|
|
99
|
+
parsed_imports = []
|
|
100
|
+
parsed_calls = []
|
|
101
|
+
# var_type_map is built from the "variables" pass so that the subsequent
|
|
102
|
+
# "calls" pass can resolve the declared type of field/local-variable
|
|
103
|
+
# receivers (e.g. `service.doWork()` -> inferred_obj_type='WorkService').
|
|
104
|
+
var_type_map: Dict[str, str] = {}
|
|
105
|
+
|
|
106
|
+
for capture_name, query in JAVA_QUERIES.items():
|
|
107
|
+
results = execute_query(self.language, query, tree.root_node)
|
|
108
|
+
|
|
109
|
+
if capture_name == "functions":
|
|
110
|
+
parsed_functions = self._parse_functions(results, source_code, path)
|
|
111
|
+
elif capture_name == "classes":
|
|
112
|
+
parsed_classes = self._parse_classes(results, source_code, path)
|
|
113
|
+
elif capture_name == "imports":
|
|
114
|
+
parsed_imports = self._parse_imports(results, source_code)
|
|
115
|
+
elif capture_name == "variables":
|
|
116
|
+
parsed_variables = self._parse_variables(results, source_code, path)
|
|
117
|
+
# Build name->type map for cross-file call resolution
|
|
118
|
+
var_type_map = {
|
|
119
|
+
v["name"]: self._strip_generic(v["type"])
|
|
120
|
+
for v in parsed_variables
|
|
121
|
+
if v.get("type") and v.get("name")
|
|
122
|
+
}
|
|
123
|
+
elif capture_name == "calls":
|
|
124
|
+
parsed_calls = self._parse_calls(results, source_code, var_type_map)
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
"path": str(path),
|
|
128
|
+
"functions": parsed_functions,
|
|
129
|
+
"classes": parsed_classes,
|
|
130
|
+
"variables": parsed_variables,
|
|
131
|
+
"imports": parsed_imports,
|
|
132
|
+
"function_calls": parsed_calls,
|
|
133
|
+
"is_dependency": is_dependency,
|
|
134
|
+
"lang": self.language_name,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
except Exception as e:
|
|
138
|
+
error_logger(f"Error parsing Java file {path}: {e}")
|
|
139
|
+
return {
|
|
140
|
+
"path": str(path),
|
|
141
|
+
"functions": [],
|
|
142
|
+
"classes": [],
|
|
143
|
+
"variables": [],
|
|
144
|
+
"imports": [],
|
|
145
|
+
"function_calls": [],
|
|
146
|
+
"is_dependency": is_dependency,
|
|
147
|
+
"lang": self.language_name,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
def _get_parent_context(self, node: Any) -> Tuple[Optional[str], Optional[str], Optional[int]]:
|
|
151
|
+
curr = node.parent
|
|
152
|
+
while curr:
|
|
153
|
+
if curr.type in ("method_declaration", "constructor_declaration"):
|
|
154
|
+
name_node = curr.child_by_field_name("name")
|
|
155
|
+
return (
|
|
156
|
+
self._get_node_text(name_node) if name_node else None,
|
|
157
|
+
curr.type,
|
|
158
|
+
curr.start_point[0] + 1,
|
|
159
|
+
)
|
|
160
|
+
if curr.type in ("class_declaration", "interface_declaration", "enum_declaration", "annotation_type_declaration"):
|
|
161
|
+
name_node = curr.child_by_field_name("name")
|
|
162
|
+
return (
|
|
163
|
+
self._get_node_text(name_node) if name_node else None,
|
|
164
|
+
curr.type,
|
|
165
|
+
curr.start_point[0] + 1,
|
|
166
|
+
)
|
|
167
|
+
curr = curr.parent
|
|
168
|
+
return None, None, None
|
|
169
|
+
|
|
170
|
+
def _get_node_text(self, node: Any) -> str:
|
|
171
|
+
if not node: return ""
|
|
172
|
+
return node.text.decode("utf-8")
|
|
173
|
+
|
|
174
|
+
def _parse_functions(self, captures: list, source_code: str, path: Path) -> list[Dict[str, Any]]:
|
|
175
|
+
functions = []
|
|
176
|
+
# Group by node identity or stable key to avoid duplicates
|
|
177
|
+
seen_nodes = set()
|
|
178
|
+
|
|
179
|
+
for node, capture_name in captures:
|
|
180
|
+
if capture_name == "function_node":
|
|
181
|
+
node_id = (node.start_byte, node.end_byte, node.type)
|
|
182
|
+
if node_id in seen_nodes:
|
|
183
|
+
continue
|
|
184
|
+
seen_nodes.add(node_id)
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
start_line = node.start_point[0] + 1
|
|
188
|
+
end_line = node.end_point[0] + 1
|
|
189
|
+
|
|
190
|
+
name_node = node.child_by_field_name("name")
|
|
191
|
+
if name_node:
|
|
192
|
+
func_name = self._get_node_text(name_node)
|
|
193
|
+
|
|
194
|
+
params_node = node.child_by_field_name("parameters")
|
|
195
|
+
parameters = []
|
|
196
|
+
if params_node:
|
|
197
|
+
params_text = self._get_node_text(params_node)
|
|
198
|
+
parameters = self._extract_parameter_names(params_text)
|
|
199
|
+
|
|
200
|
+
source_text = self._get_node_text(node)
|
|
201
|
+
|
|
202
|
+
# Get class context
|
|
203
|
+
context_name, context_type, context_line = self._get_parent_context(node)
|
|
204
|
+
|
|
205
|
+
func_data = {
|
|
206
|
+
"name": func_name,
|
|
207
|
+
"parameters": parameters,
|
|
208
|
+
"line_number": start_line,
|
|
209
|
+
"end_line": end_line,
|
|
210
|
+
"path": str(path),
|
|
211
|
+
"lang": self.language_name,
|
|
212
|
+
"context": context_name,
|
|
213
|
+
"class_context": context_name if context_type and "class" in context_type else None
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if self.index_source:
|
|
217
|
+
func_data["source"] = source_text
|
|
218
|
+
|
|
219
|
+
functions.append(func_data)
|
|
220
|
+
|
|
221
|
+
except Exception as e:
|
|
222
|
+
error_logger(f"Error parsing function in {path}: {e}")
|
|
223
|
+
continue
|
|
224
|
+
|
|
225
|
+
return functions
|
|
226
|
+
|
|
227
|
+
def _parse_classes(self, captures: list, source_code: str, path: Path) -> list[Dict[str, Any]]:
|
|
228
|
+
classes = []
|
|
229
|
+
seen_nodes = set()
|
|
230
|
+
|
|
231
|
+
for node, capture_name in captures:
|
|
232
|
+
if capture_name == "class":
|
|
233
|
+
node_id = (node.start_byte, node.end_byte, node.type)
|
|
234
|
+
if node_id in seen_nodes:
|
|
235
|
+
continue
|
|
236
|
+
seen_nodes.add(node_id)
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
start_line = node.start_point[0] + 1
|
|
240
|
+
end_line = node.end_point[0] + 1
|
|
241
|
+
|
|
242
|
+
name_node = node.child_by_field_name("name")
|
|
243
|
+
if name_node:
|
|
244
|
+
class_name = self._get_node_text(name_node)
|
|
245
|
+
source_text = self._get_node_text(node)
|
|
246
|
+
|
|
247
|
+
bases = []
|
|
248
|
+
# Look for superclass (extends)
|
|
249
|
+
superclass_node = node.child_by_field_name('superclass')
|
|
250
|
+
if superclass_node:
|
|
251
|
+
# In Java, superclass field usually points to a type node
|
|
252
|
+
bases.append(self._get_node_text(superclass_node))
|
|
253
|
+
|
|
254
|
+
# Look for super_interfaces (implements)
|
|
255
|
+
interfaces_node = node.child_by_field_name('interfaces')
|
|
256
|
+
if not interfaces_node:
|
|
257
|
+
interfaces_node = next((c for c in node.children if c.type == 'super_interfaces'), None)
|
|
258
|
+
|
|
259
|
+
if interfaces_node:
|
|
260
|
+
type_list = interfaces_node.child_by_field_name('list')
|
|
261
|
+
if not type_list:
|
|
262
|
+
type_list = next((c for c in interfaces_node.children if c.type == 'type_list'), None)
|
|
263
|
+
|
|
264
|
+
if type_list:
|
|
265
|
+
for child in type_list.children:
|
|
266
|
+
if child.type in ('type_identifier', 'generic_type', 'scoped_type_identifier'):
|
|
267
|
+
bases.append(self._get_node_text(child))
|
|
268
|
+
else:
|
|
269
|
+
for child in interfaces_node.children:
|
|
270
|
+
if child.type in ('type_identifier', 'generic_type', 'scoped_type_identifier'):
|
|
271
|
+
bases.append(self._get_node_text(child))
|
|
272
|
+
|
|
273
|
+
class_data = {
|
|
274
|
+
"name": class_name,
|
|
275
|
+
"line_number": start_line,
|
|
276
|
+
"end_line": end_line,
|
|
277
|
+
"bases": bases,
|
|
278
|
+
"path": str(path),
|
|
279
|
+
"lang": self.language_name,
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if self.index_source:
|
|
283
|
+
class_data["source"] = source_text
|
|
284
|
+
|
|
285
|
+
classes.append(class_data)
|
|
286
|
+
|
|
287
|
+
except Exception as e:
|
|
288
|
+
error_logger(f"Error parsing class in {path}: {e}")
|
|
289
|
+
continue
|
|
290
|
+
|
|
291
|
+
return classes
|
|
292
|
+
|
|
293
|
+
def _parse_variables(self, captures: list, source_code: str, path: Path) -> list[Dict[str, Any]]:
|
|
294
|
+
variables = []
|
|
295
|
+
seen_vars = set()
|
|
296
|
+
|
|
297
|
+
for node, capture_name in captures:
|
|
298
|
+
if capture_name == "variable":
|
|
299
|
+
# The capture is on the whole declaration, we look for name/type children or captures
|
|
300
|
+
# But our query captures @name and @type separately on subnodes.
|
|
301
|
+
# Actually, the query structure:
|
|
302
|
+
# (local_variable_declaration ... declarator: (variable_declarator name: (identifier) @name)) @variable
|
|
303
|
+
# This means we get 'variable', 'type', 'name' captures in sequence.
|
|
304
|
+
# We should iterate and group them.
|
|
305
|
+
pass
|
|
306
|
+
|
|
307
|
+
# Re-approach: Iterate captures and collect finding.
|
|
308
|
+
# Tree sitter returns a list of (node, capture_name).
|
|
309
|
+
|
|
310
|
+
# Simpler approach: Iterate 'name' captures that are inside a variable declaration context
|
|
311
|
+
|
|
312
|
+
current_var = {}
|
|
313
|
+
|
|
314
|
+
for node, capture_name in captures:
|
|
315
|
+
if capture_name == "name":
|
|
316
|
+
# Check parent to confirm it's a variable declarator
|
|
317
|
+
if node.parent.type == "variable_declarator":
|
|
318
|
+
var_name = self._get_node_text(node)
|
|
319
|
+
start_line = node.start_point[0] + 1
|
|
320
|
+
|
|
321
|
+
# Get type? Type is sibling of declarator usually, or child of declaration
|
|
322
|
+
# local_variable_declaration -> type, variable_declarator
|
|
323
|
+
declaration = node.parent.parent
|
|
324
|
+
type_node = declaration.child_by_field_name("type")
|
|
325
|
+
var_type = self._get_node_text(type_node) if type_node else "Unknown"
|
|
326
|
+
|
|
327
|
+
start_byte = node.start_byte
|
|
328
|
+
if start_byte in seen_vars:
|
|
329
|
+
continue
|
|
330
|
+
seen_vars.add(start_byte)
|
|
331
|
+
|
|
332
|
+
ctx_name, ctx_type, ctx_line = self._get_parent_context(node)
|
|
333
|
+
|
|
334
|
+
variables.append({
|
|
335
|
+
"name": var_name,
|
|
336
|
+
"type": var_type,
|
|
337
|
+
"line_number": start_line,
|
|
338
|
+
"path": str(path),
|
|
339
|
+
"lang": self.language_name,
|
|
340
|
+
"context": ctx_name,
|
|
341
|
+
"class_context": ctx_name if ctx_type and "class" in ctx_type else None
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
return variables
|
|
345
|
+
|
|
346
|
+
def _parse_imports(self, captures: list, source_code: str) -> list[dict]:
|
|
347
|
+
imports = []
|
|
348
|
+
|
|
349
|
+
for node, capture_name in captures:
|
|
350
|
+
if capture_name == "import":
|
|
351
|
+
try:
|
|
352
|
+
import_text = self._get_node_text(node)
|
|
353
|
+
import_match = re.search(r'import\s+(?:static\s+)?([^;]+)', import_text)
|
|
354
|
+
if import_match:
|
|
355
|
+
import_path = import_match.group(1).strip()
|
|
356
|
+
|
|
357
|
+
import_data = {
|
|
358
|
+
"name": import_path,
|
|
359
|
+
"full_import_name": import_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
|
+
imports.append(import_data)
|
|
367
|
+
except Exception as e:
|
|
368
|
+
error_logger(f"Error parsing import: {e}")
|
|
369
|
+
continue
|
|
370
|
+
|
|
371
|
+
return imports
|
|
372
|
+
|
|
373
|
+
def _parse_calls(self, captures: list, source_code: str, var_type_map: Optional[Dict[str, str]] = None) -> list[dict]:
|
|
374
|
+
calls = []
|
|
375
|
+
seen_calls = set()
|
|
376
|
+
if var_type_map is None:
|
|
377
|
+
var_type_map = {}
|
|
378
|
+
|
|
379
|
+
debug_log(f"Processing {len(captures)} captures for calls")
|
|
380
|
+
|
|
381
|
+
for node, capture_name in captures:
|
|
382
|
+
if capture_name == "name":
|
|
383
|
+
try:
|
|
384
|
+
call_name = self._get_node_text(node)
|
|
385
|
+
line_number = node.start_point[0] + 1
|
|
386
|
+
|
|
387
|
+
# Ensure we identify the full call node
|
|
388
|
+
call_node = node.parent
|
|
389
|
+
while call_node and call_node.type not in ("method_invocation", "object_creation_expression"):
|
|
390
|
+
call_node = call_node.parent
|
|
391
|
+
|
|
392
|
+
if not call_node:
|
|
393
|
+
# fallback if we matched a loose identifier
|
|
394
|
+
call_node = node
|
|
395
|
+
|
|
396
|
+
# Avoid duplicates
|
|
397
|
+
call_key = f"{call_name}_{line_number}"
|
|
398
|
+
if call_key in seen_calls:
|
|
399
|
+
continue
|
|
400
|
+
seen_calls.add(call_key)
|
|
401
|
+
|
|
402
|
+
# Extract arguments
|
|
403
|
+
args = []
|
|
404
|
+
if call_node:
|
|
405
|
+
args_node = next((c for c in call_node.children if c.type == 'argument_list'), None)
|
|
406
|
+
if args_node:
|
|
407
|
+
for arg in args_node.children:
|
|
408
|
+
if arg.type not in ('(', ')', ','):
|
|
409
|
+
args.append(self._get_node_text(arg))
|
|
410
|
+
|
|
411
|
+
# Extract meaningful full_name and infer the receiver's declared type.
|
|
412
|
+
# When a method is called on a field or local variable whose type was
|
|
413
|
+
# declared in this file (e.g. `private WorkService workService;`),
|
|
414
|
+
# we populate inferred_obj_type so that resolve_function_call can
|
|
415
|
+
# look it up in imports_map and create an accurate cross-file CALLS edge.
|
|
416
|
+
full_name = call_name
|
|
417
|
+
inferred_obj_type = None
|
|
418
|
+
if call_node.type == 'method_invocation':
|
|
419
|
+
obj_node = call_node.child_by_field_name('object')
|
|
420
|
+
if obj_node:
|
|
421
|
+
obj_text = self._get_node_text(obj_node)
|
|
422
|
+
full_name = f"{obj_text}.{call_name}"
|
|
423
|
+
# Only resolve simple identifiers (not chained calls like foo.bar().baz())
|
|
424
|
+
base_obj = obj_text.split(".")[0]
|
|
425
|
+
if "(" not in base_obj and base_obj in var_type_map:
|
|
426
|
+
inferred_obj_type = var_type_map[base_obj]
|
|
427
|
+
elif call_node.type == 'object_creation_expression':
|
|
428
|
+
type_node = call_node.child_by_field_name('type')
|
|
429
|
+
if type_node:
|
|
430
|
+
full_name = self._get_node_text(type_node)
|
|
431
|
+
|
|
432
|
+
ctx_name, ctx_type, ctx_line = self._get_parent_context(node)
|
|
433
|
+
|
|
434
|
+
debug_log(f"Found call: {call_name} (full_name: {full_name}, inferred_obj_type: {inferred_obj_type}, args: {args}) in context {ctx_name}")
|
|
435
|
+
|
|
436
|
+
call_data = {
|
|
437
|
+
"name": call_name,
|
|
438
|
+
"full_name": full_name,
|
|
439
|
+
"line_number": line_number,
|
|
440
|
+
"args": args,
|
|
441
|
+
"inferred_obj_type": inferred_obj_type,
|
|
442
|
+
"context": (ctx_name, ctx_type, ctx_line),
|
|
443
|
+
"class_context": (ctx_name, ctx_line) if ctx_type and "class" in ctx_type else (None, None),
|
|
444
|
+
"lang": self.language_name,
|
|
445
|
+
"is_dependency": False,
|
|
446
|
+
}
|
|
447
|
+
calls.append(call_data)
|
|
448
|
+
except Exception as e:
|
|
449
|
+
error_logger(f"Error parsing call: {e}")
|
|
450
|
+
continue
|
|
451
|
+
|
|
452
|
+
return calls
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _extract_parameter_names(self, params_text: str) -> list[str]:
|
|
456
|
+
params = []
|
|
457
|
+
if not params_text or params_text.strip() == "()":
|
|
458
|
+
return params
|
|
459
|
+
|
|
460
|
+
params_content = params_text.strip("()")
|
|
461
|
+
if not params_content:
|
|
462
|
+
return params
|
|
463
|
+
|
|
464
|
+
for param in params_content.split(","):
|
|
465
|
+
param = param.strip()
|
|
466
|
+
if param:
|
|
467
|
+
parts = param.split()
|
|
468
|
+
if len(parts) >= 2:
|
|
469
|
+
param_name = parts[-1]
|
|
470
|
+
params.append(param_name)
|
|
471
|
+
|
|
472
|
+
return params
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def pre_scan_java(files: list[Path], parser_wrapper) -> dict:
|
|
476
|
+
name_to_files = {}
|
|
477
|
+
|
|
478
|
+
for path in files:
|
|
479
|
+
try:
|
|
480
|
+
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
481
|
+
content = f.read()
|
|
482
|
+
|
|
483
|
+
class_matches = re.finditer(r'\b(?:public\s+|private\s+|protected\s+)?(?:static\s+)?(?:abstract\s+)?(?:final\s+)?class\s+(\w+)', content)
|
|
484
|
+
for match in class_matches:
|
|
485
|
+
class_name = match.group(1)
|
|
486
|
+
if class_name not in name_to_files:
|
|
487
|
+
name_to_files[class_name] = []
|
|
488
|
+
name_to_files[class_name].append(str(path))
|
|
489
|
+
|
|
490
|
+
interface_matches = re.finditer(r'\b(?:public\s+|private\s+|protected\s+)?interface\s+(\w+)', content)
|
|
491
|
+
for match in interface_matches:
|
|
492
|
+
interface_name = match.group(1)
|
|
493
|
+
if interface_name not in name_to_files:
|
|
494
|
+
name_to_files[interface_name] = []
|
|
495
|
+
name_to_files[interface_name].append(str(path))
|
|
496
|
+
|
|
497
|
+
except Exception as e:
|
|
498
|
+
error_logger(f"Error pre-scanning Java file {path}: {e}")
|
|
499
|
+
|
|
500
|
+
return name_to_files
|