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,537 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any, Dict, Optional, Tuple
|
|
3
|
+
from codegraphcontext.utils.debug_log import debug_log, info_logger, error_logger, warning_logger, debug_logger
|
|
4
|
+
from codegraphcontext.utils.tree_sitter_manager import execute_query
|
|
5
|
+
|
|
6
|
+
RUBY_QUERIES = {
|
|
7
|
+
"functions": """
|
|
8
|
+
(method
|
|
9
|
+
name: (identifier) @name
|
|
10
|
+
) @function_node
|
|
11
|
+
""",
|
|
12
|
+
"classes": """
|
|
13
|
+
(class
|
|
14
|
+
name: (constant) @name
|
|
15
|
+
) @class
|
|
16
|
+
""",
|
|
17
|
+
"modules": """
|
|
18
|
+
(module
|
|
19
|
+
name: (constant) @name
|
|
20
|
+
) @module_node
|
|
21
|
+
""",
|
|
22
|
+
"imports": """
|
|
23
|
+
(call
|
|
24
|
+
method: (identifier) @method_name
|
|
25
|
+
arguments: (argument_list
|
|
26
|
+
(string) @path
|
|
27
|
+
)
|
|
28
|
+
) @import
|
|
29
|
+
""",
|
|
30
|
+
"calls": """
|
|
31
|
+
(call
|
|
32
|
+
receiver: (_)? @receiver
|
|
33
|
+
method: (identifier) @name
|
|
34
|
+
arguments: (argument_list)? @args
|
|
35
|
+
) @call_node
|
|
36
|
+
""",
|
|
37
|
+
"variables": """
|
|
38
|
+
(assignment
|
|
39
|
+
left: (identifier) @name
|
|
40
|
+
right: (_) @value
|
|
41
|
+
)
|
|
42
|
+
(assignment
|
|
43
|
+
left: (instance_variable) @name
|
|
44
|
+
right: (_) @value
|
|
45
|
+
)
|
|
46
|
+
""",
|
|
47
|
+
"comments": """
|
|
48
|
+
(comment) @comment
|
|
49
|
+
""",
|
|
50
|
+
"module_includes": """
|
|
51
|
+
(call
|
|
52
|
+
method: (identifier) @method
|
|
53
|
+
arguments: (argument_list (constant) @module)
|
|
54
|
+
) @include_call
|
|
55
|
+
""",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class RubyTreeSitterParser:
|
|
60
|
+
"""A Ruby-specific parser using tree-sitter."""
|
|
61
|
+
|
|
62
|
+
def __init__(self, generic_parser_wrapper: Any):
|
|
63
|
+
self.generic_parser_wrapper = generic_parser_wrapper
|
|
64
|
+
self.language_name = "ruby"
|
|
65
|
+
self.language = generic_parser_wrapper.language
|
|
66
|
+
self.parser = generic_parser_wrapper.parser
|
|
67
|
+
|
|
68
|
+
def _get_node_text(self, node: Any) -> str:
|
|
69
|
+
return node.text.decode("utf-8")
|
|
70
|
+
|
|
71
|
+
def _enclosing_class_name(self, node: Any) -> Optional[str]:
|
|
72
|
+
name, typ, _ = self._get_parent_context(node, ('class',))
|
|
73
|
+
return name
|
|
74
|
+
|
|
75
|
+
def _find_modules(self, root_node: Any) -> list[Dict[str, Any]]:
|
|
76
|
+
modules = []
|
|
77
|
+
query_str = RUBY_QUERIES["modules"]
|
|
78
|
+
# name via captures
|
|
79
|
+
captures = list(execute_query(self.language, query_str, root_node))
|
|
80
|
+
for node, cap in captures:
|
|
81
|
+
if cap == "module_node":
|
|
82
|
+
name = None
|
|
83
|
+
for n, c in captures:
|
|
84
|
+
if c == "name":
|
|
85
|
+
if n.start_byte >= node.start_byte and n.end_byte <= node.end_byte:
|
|
86
|
+
name = self._get_node_text(n)
|
|
87
|
+
break
|
|
88
|
+
if name:
|
|
89
|
+
module_data = {
|
|
90
|
+
"name": name,
|
|
91
|
+
"line_number": node.start_point[0] + 1,
|
|
92
|
+
"end_line": node.end_point[0] + 1,
|
|
93
|
+
"lang": self.language_name,
|
|
94
|
+
"is_dependency": False,
|
|
95
|
+
}
|
|
96
|
+
if self.index_source:
|
|
97
|
+
module_data["source"] = self._get_node_text(node)
|
|
98
|
+
|
|
99
|
+
modules.append(module_data)
|
|
100
|
+
return modules
|
|
101
|
+
|
|
102
|
+
def _find_module_inclusions(self, root_node: Any) -> list[Dict[str, Any]]:
|
|
103
|
+
includes = []
|
|
104
|
+
query_str = RUBY_QUERIES["module_includes"]
|
|
105
|
+
for node, cap in execute_query(self.language, query_str, root_node):
|
|
106
|
+
if cap == "method":
|
|
107
|
+
method_name = self._get_node_text(node)
|
|
108
|
+
if method_name != "include":
|
|
109
|
+
continue
|
|
110
|
+
if cap == "include_call":
|
|
111
|
+
method = None
|
|
112
|
+
module = None
|
|
113
|
+
for n, c in execute_query(self.language, query_str, node):
|
|
114
|
+
if c == "method":
|
|
115
|
+
method = self._get_node_text(n)
|
|
116
|
+
elif c == "module":
|
|
117
|
+
module = self._get_node_text(n)
|
|
118
|
+
if method == "include" and module:
|
|
119
|
+
cls = self._enclosing_class_name(node)
|
|
120
|
+
if cls:
|
|
121
|
+
includes.append({
|
|
122
|
+
"class": cls,
|
|
123
|
+
"module": module,
|
|
124
|
+
"line_number": node.start_point[0] + 1,
|
|
125
|
+
"lang": self.language_name,
|
|
126
|
+
"is_dependency": False,
|
|
127
|
+
})
|
|
128
|
+
return includes
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _get_parent_context(self, node: Any, types: Tuple[str, ...] = ('class', 'module', 'method')):
|
|
132
|
+
"""Find parent context for Ruby constructs."""
|
|
133
|
+
curr = node.parent
|
|
134
|
+
while curr:
|
|
135
|
+
if curr.type in types:
|
|
136
|
+
name_node = curr.child_by_field_name('name')
|
|
137
|
+
if name_node:
|
|
138
|
+
return self._get_node_text(name_node), curr.type, curr.start_point[0] + 1
|
|
139
|
+
curr = curr.parent
|
|
140
|
+
return None, None, None
|
|
141
|
+
|
|
142
|
+
def _calculate_complexity(self, node: Any) -> int:
|
|
143
|
+
"""Calculate cyclomatic complexity for Ruby constructs."""
|
|
144
|
+
complexity_nodes = {
|
|
145
|
+
"if", "unless", "case", "when", "while", "until", "for", "rescue", "ensure",
|
|
146
|
+
"and", "or", "&&", "||", "?", "ternary"
|
|
147
|
+
}
|
|
148
|
+
count = 1
|
|
149
|
+
|
|
150
|
+
def traverse(n):
|
|
151
|
+
nonlocal count
|
|
152
|
+
if n.type in complexity_nodes:
|
|
153
|
+
count += 1
|
|
154
|
+
for child in n.children:
|
|
155
|
+
traverse(child)
|
|
156
|
+
|
|
157
|
+
traverse(node)
|
|
158
|
+
return count
|
|
159
|
+
|
|
160
|
+
def _get_docstring(self, node: Any) -> Optional[str]:
|
|
161
|
+
"""Extract comments as docstrings for Ruby constructs."""
|
|
162
|
+
# Look for comments before the node
|
|
163
|
+
prev_sibling = node.prev_sibling
|
|
164
|
+
while prev_sibling and prev_sibling.type in ('comment', '\n', ' '):
|
|
165
|
+
if prev_sibling.type == 'comment':
|
|
166
|
+
comment_text = self._get_node_text(prev_sibling)
|
|
167
|
+
if comment_text.startswith('#') and not comment_text.startswith('#!'):
|
|
168
|
+
return comment_text.strip()
|
|
169
|
+
prev_sibling = prev_sibling.prev_sibling
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
def _parse_method_parameters(self, method_node: Any) -> list[str]:
|
|
173
|
+
"""Parse method parameters from a method node."""
|
|
174
|
+
params = []
|
|
175
|
+
# Look for parameters in the method node
|
|
176
|
+
for child in method_node.children:
|
|
177
|
+
if child.type == 'identifier' and child != method_node.child_by_field_name('name'):
|
|
178
|
+
# This is likely a parameter
|
|
179
|
+
params.append(self._get_node_text(child))
|
|
180
|
+
return params
|
|
181
|
+
|
|
182
|
+
def parse(self, path: Path, is_dependency: bool = False, index_source: bool = False) -> Dict[str, Any]:
|
|
183
|
+
"""Parses a Ruby file and returns its structure."""
|
|
184
|
+
self.index_source = index_source
|
|
185
|
+
with open(path, "r", encoding="utf-8", errors="ignore") as f:
|
|
186
|
+
source_code = f.read()
|
|
187
|
+
|
|
188
|
+
tree = self.parser.parse(bytes(source_code, "utf8"))
|
|
189
|
+
root_node = tree.root_node
|
|
190
|
+
|
|
191
|
+
functions = self._find_functions(root_node)
|
|
192
|
+
classes = self._find_classes(root_node)
|
|
193
|
+
imports = self._find_imports(root_node)
|
|
194
|
+
function_calls = self._find_calls(root_node)
|
|
195
|
+
variables = self._find_variables(root_node)
|
|
196
|
+
modules = self._find_modules(root_node)
|
|
197
|
+
module_inclusions = self._find_module_inclusions(root_node)
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
"path": str(path),
|
|
201
|
+
"functions": functions,
|
|
202
|
+
"classes": classes,
|
|
203
|
+
"variables": variables,
|
|
204
|
+
"imports": imports,
|
|
205
|
+
"function_calls": function_calls,
|
|
206
|
+
"is_dependency": is_dependency,
|
|
207
|
+
"lang": self.language_name,
|
|
208
|
+
"modules": modules,
|
|
209
|
+
"module_inclusions": module_inclusions,
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
def _find_functions(self, root_node: Any) -> list[Dict[str, Any]]:
|
|
213
|
+
"""Find all function/method definitions."""
|
|
214
|
+
functions = []
|
|
215
|
+
query_str = RUBY_QUERIES["functions"]
|
|
216
|
+
|
|
217
|
+
# Collect all captures first
|
|
218
|
+
all_captures = list(execute_query(self.language, query_str, root_node))
|
|
219
|
+
|
|
220
|
+
# Group captures by function node using a different approach
|
|
221
|
+
captures_by_function = {}
|
|
222
|
+
for node, capture_name in all_captures:
|
|
223
|
+
if capture_name == 'function_node':
|
|
224
|
+
captures_by_function[id(node)] = {'node': node, 'name': None}
|
|
225
|
+
|
|
226
|
+
# Now find names for each function
|
|
227
|
+
for node, capture_name in all_captures:
|
|
228
|
+
if capture_name == 'name':
|
|
229
|
+
# Find which function this name belongs to
|
|
230
|
+
for func_id, func_data in captures_by_function.items():
|
|
231
|
+
func_node = func_data['node']
|
|
232
|
+
# Check if this name node is within the function node
|
|
233
|
+
if (node.start_byte >= func_node.start_byte and
|
|
234
|
+
node.end_byte <= func_node.end_byte):
|
|
235
|
+
captures_by_function[func_id]['name'] = self._get_node_text(node)
|
|
236
|
+
break
|
|
237
|
+
|
|
238
|
+
# Build function entries
|
|
239
|
+
for func_data in captures_by_function.values():
|
|
240
|
+
func_node = func_data['node']
|
|
241
|
+
name = func_data['name']
|
|
242
|
+
|
|
243
|
+
if name:
|
|
244
|
+
args = self._parse_method_parameters(func_node)
|
|
245
|
+
|
|
246
|
+
# Get context and docstring
|
|
247
|
+
context, context_type, _ = self._get_parent_context(func_node)
|
|
248
|
+
class_context = context if context_type in ('class', 'module') else None
|
|
249
|
+
docstring = self._get_docstring(func_node)
|
|
250
|
+
|
|
251
|
+
func_data = {
|
|
252
|
+
"name": name,
|
|
253
|
+
"line_number": func_node.start_point[0] + 1,
|
|
254
|
+
"end_line": func_node.end_point[0] + 1,
|
|
255
|
+
"args": args,
|
|
256
|
+
"lang": self.language_name,
|
|
257
|
+
"is_dependency": False,
|
|
258
|
+
}
|
|
259
|
+
if self.index_source:
|
|
260
|
+
func_data["source"] = self._get_node_text(func_node)
|
|
261
|
+
func_data["docstring"] = docstring
|
|
262
|
+
|
|
263
|
+
functions.append(func_data)
|
|
264
|
+
|
|
265
|
+
return functions
|
|
266
|
+
|
|
267
|
+
def _find_classes(self, root_node: Any) -> list[Dict[str, Any]]:
|
|
268
|
+
"""Find all class and module definitions."""
|
|
269
|
+
classes = []
|
|
270
|
+
query_str = RUBY_QUERIES["classes"]
|
|
271
|
+
|
|
272
|
+
# Collect all captures first
|
|
273
|
+
all_captures = list(execute_query(self.language, query_str, root_node))
|
|
274
|
+
|
|
275
|
+
# Group captures by class node using a different approach
|
|
276
|
+
captures_by_class = {}
|
|
277
|
+
for node, capture_name in all_captures:
|
|
278
|
+
if capture_name == 'class':
|
|
279
|
+
captures_by_class[id(node)] = {'node': node, 'name': None}
|
|
280
|
+
|
|
281
|
+
# Now find names for each class
|
|
282
|
+
for node, capture_name in all_captures:
|
|
283
|
+
if capture_name == 'name':
|
|
284
|
+
# Find which class this name belongs to
|
|
285
|
+
for class_id, class_data in captures_by_class.items():
|
|
286
|
+
class_node = class_data['node']
|
|
287
|
+
# Check if this name node is within the class node
|
|
288
|
+
if (node.start_byte >= class_node.start_byte and
|
|
289
|
+
node.end_byte <= class_node.end_byte):
|
|
290
|
+
captures_by_class[class_id]['name'] = self._get_node_text(node)
|
|
291
|
+
break
|
|
292
|
+
|
|
293
|
+
# Build class entries
|
|
294
|
+
for class_data in captures_by_class.values():
|
|
295
|
+
class_node = class_data['node']
|
|
296
|
+
name = class_data['name']
|
|
297
|
+
|
|
298
|
+
if name:
|
|
299
|
+
# Get superclass for inheritance (simplified)
|
|
300
|
+
bases = []
|
|
301
|
+
|
|
302
|
+
# Get context and docstring
|
|
303
|
+
context, context_type, _ = self._get_parent_context(class_node)
|
|
304
|
+
class_context = context if context_type in ('class', 'module') else None
|
|
305
|
+
docstring = self._get_docstring(class_node)
|
|
306
|
+
|
|
307
|
+
class_data = {
|
|
308
|
+
"name": name,
|
|
309
|
+
"line_number": class_node.start_point[0] + 1,
|
|
310
|
+
"end_line": class_node.end_point[0] + 1,
|
|
311
|
+
"bases": bases,
|
|
312
|
+
"context": context,
|
|
313
|
+
"context_type": context_type,
|
|
314
|
+
"class_context": class_context,
|
|
315
|
+
"decorators": [],
|
|
316
|
+
"lang": self.language_name,
|
|
317
|
+
"is_dependency": False,
|
|
318
|
+
}
|
|
319
|
+
if self.index_source:
|
|
320
|
+
class_data["source"] = self._get_node_text(class_node)
|
|
321
|
+
class_data["docstring"] = docstring
|
|
322
|
+
|
|
323
|
+
classes.append(class_data)
|
|
324
|
+
|
|
325
|
+
return classes
|
|
326
|
+
|
|
327
|
+
def _find_imports(self, root_node: Any) -> list[Dict[str, Any]]:
|
|
328
|
+
"""Find all require/load statements."""
|
|
329
|
+
imports = []
|
|
330
|
+
query_str = RUBY_QUERIES["imports"]
|
|
331
|
+
|
|
332
|
+
# Collect all captures first
|
|
333
|
+
all_captures = list(execute_query(self.language, query_str, root_node))
|
|
334
|
+
|
|
335
|
+
# Group captures by import node using a different approach
|
|
336
|
+
captures_by_import = {}
|
|
337
|
+
for node, capture_name in all_captures:
|
|
338
|
+
if capture_name == 'import':
|
|
339
|
+
captures_by_import[id(node)] = {'node': node, 'method_name': None, 'path': None}
|
|
340
|
+
|
|
341
|
+
# Now find method names and paths for each import
|
|
342
|
+
for node, capture_name in all_captures:
|
|
343
|
+
if capture_name == 'method_name':
|
|
344
|
+
# Find which import this method name belongs to
|
|
345
|
+
for import_id, import_data in captures_by_import.items():
|
|
346
|
+
import_node = import_data['node']
|
|
347
|
+
# Check if this method name node is within the import node
|
|
348
|
+
if (node.start_byte >= import_node.start_byte and
|
|
349
|
+
node.end_byte <= import_node.end_byte):
|
|
350
|
+
captures_by_import[import_id]['method_name'] = self._get_node_text(node)
|
|
351
|
+
break
|
|
352
|
+
elif capture_name == 'path':
|
|
353
|
+
# Find which import this path belongs to
|
|
354
|
+
for import_id, import_data in captures_by_import.items():
|
|
355
|
+
import_node = import_data['node']
|
|
356
|
+
# Check if this path node is within the import node
|
|
357
|
+
if (node.start_byte >= import_node.start_byte and
|
|
358
|
+
node.end_byte <= import_node.end_byte):
|
|
359
|
+
captures_by_import[import_id]['path'] = self._get_node_text(node)
|
|
360
|
+
break
|
|
361
|
+
|
|
362
|
+
# Build import entries
|
|
363
|
+
for import_data in captures_by_import.values():
|
|
364
|
+
import_node = import_data['node']
|
|
365
|
+
method_name = import_data['method_name']
|
|
366
|
+
path = import_data['path']
|
|
367
|
+
|
|
368
|
+
if method_name and path:
|
|
369
|
+
path = path.strip('\'"')
|
|
370
|
+
|
|
371
|
+
# Only process require/load statements
|
|
372
|
+
if method_name in ('require', 'require_relative', 'load'):
|
|
373
|
+
imports.append({
|
|
374
|
+
"name": path,
|
|
375
|
+
"full_import_name": f"{method_name} '{path}'",
|
|
376
|
+
"line_number": import_node.start_point[0] + 1,
|
|
377
|
+
"alias": None,
|
|
378
|
+
"lang": self.language_name,
|
|
379
|
+
"is_dependency": False,
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
return imports
|
|
383
|
+
|
|
384
|
+
def _find_calls(self, root_node: Any) -> list[Dict[str, Any]]:
|
|
385
|
+
"""Find all function and method calls."""
|
|
386
|
+
calls = []
|
|
387
|
+
query_str = RUBY_QUERIES["calls"]
|
|
388
|
+
|
|
389
|
+
# Collect all captures
|
|
390
|
+
all_captures = list(execute_query(self.language, query_str, root_node))
|
|
391
|
+
|
|
392
|
+
# Group by call node
|
|
393
|
+
captures_by_call = {}
|
|
394
|
+
for node, capture_name in all_captures:
|
|
395
|
+
if capture_name == 'call_node':
|
|
396
|
+
captures_by_call[id(node)] = {'node': node, 'name': None, 'receiver': None, 'args': []}
|
|
397
|
+
|
|
398
|
+
for node, capture_name in all_captures:
|
|
399
|
+
for call_id, call_data in captures_by_call.items():
|
|
400
|
+
call_node = call_data['node']
|
|
401
|
+
if not (node.start_byte >= call_node.start_byte and node.end_byte <= call_node.end_byte):
|
|
402
|
+
continue
|
|
403
|
+
|
|
404
|
+
if capture_name == 'name':
|
|
405
|
+
# The identifier could be part of receiver or arguments too, be careful
|
|
406
|
+
# But tree-sitter structure ensures method name is distinct
|
|
407
|
+
# Check if node is child 'method' of call_node
|
|
408
|
+
if node == call_node.child_by_field_name('method'):
|
|
409
|
+
captures_by_call[call_id]['name'] = self._get_node_text(node)
|
|
410
|
+
|
|
411
|
+
elif capture_name == 'receiver':
|
|
412
|
+
captures_by_call[call_id]['receiver'] = self._get_node_text(node)
|
|
413
|
+
|
|
414
|
+
elif capture_name == 'args':
|
|
415
|
+
# Capture arguments
|
|
416
|
+
args_text = self._get_node_text(node)
|
|
417
|
+
# Simple heuristic: split by comma
|
|
418
|
+
captures_by_call[call_id]['args'] = [a.strip() for a in args_text.strip("()").split(',') if a.strip()]
|
|
419
|
+
|
|
420
|
+
for call_data in captures_by_call.values():
|
|
421
|
+
call_node = call_data['node']
|
|
422
|
+
name = call_data['name']
|
|
423
|
+
|
|
424
|
+
if name:
|
|
425
|
+
receiver = call_data['receiver']
|
|
426
|
+
full_name = f"{receiver}.{name}" if receiver else name
|
|
427
|
+
|
|
428
|
+
context_name, context_type, context_line = self._get_parent_context(call_node)
|
|
429
|
+
class_context = context_name if context_type in ('class', 'module') else None
|
|
430
|
+
if context_type == 'method':
|
|
431
|
+
# If inside a method, try to find enclosing class too
|
|
432
|
+
enclosing_class, _, _ = self._get_parent_context(call_node.parent, ('class', 'module'))
|
|
433
|
+
class_context = enclosing_class
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
calls.append({
|
|
437
|
+
"name": name,
|
|
438
|
+
"full_name": full_name,
|
|
439
|
+
"line_number": call_node.start_point[0] + 1,
|
|
440
|
+
"args": call_data['args'],
|
|
441
|
+
"inferred_obj_type": None,
|
|
442
|
+
"context": (context_name, context_type, context_line),
|
|
443
|
+
"class_context": class_context,
|
|
444
|
+
"lang": self.language_name,
|
|
445
|
+
"is_dependency": False,
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
return calls
|
|
449
|
+
|
|
450
|
+
def _find_variables(self, root_node: Any) -> list[Dict[str, Any]]:
|
|
451
|
+
"""Find all variable assignments."""
|
|
452
|
+
variables = []
|
|
453
|
+
query_str = RUBY_QUERIES["variables"]
|
|
454
|
+
|
|
455
|
+
# Group captures by assignment node
|
|
456
|
+
captures_by_assignment = {}
|
|
457
|
+
for node, capture_name in execute_query(self.language, query_str, root_node):
|
|
458
|
+
if capture_name == 'name':
|
|
459
|
+
# Find the parent assignment node
|
|
460
|
+
current = node.parent
|
|
461
|
+
while current and current.type != 'assignment':
|
|
462
|
+
current = current.parent
|
|
463
|
+
if current:
|
|
464
|
+
assignment_id = id(current)
|
|
465
|
+
if assignment_id not in captures_by_assignment:
|
|
466
|
+
captures_by_assignment[assignment_id] = {'node': current, 'name': None, 'value': None}
|
|
467
|
+
captures_by_assignment[assignment_id]['name'] = self._get_node_text(node)
|
|
468
|
+
elif capture_name == 'value':
|
|
469
|
+
# Find the parent assignment node
|
|
470
|
+
current = node.parent
|
|
471
|
+
while current and current.type != 'assignment':
|
|
472
|
+
current = current.parent
|
|
473
|
+
if current:
|
|
474
|
+
assignment_id = id(current)
|
|
475
|
+
if assignment_id not in captures_by_assignment:
|
|
476
|
+
captures_by_assignment[assignment_id] = {'node': current, 'name': None, 'value': None}
|
|
477
|
+
captures_by_assignment[assignment_id]['value'] = self._get_node_text(node)
|
|
478
|
+
|
|
479
|
+
# Build variable entries
|
|
480
|
+
for var_data in captures_by_assignment.values():
|
|
481
|
+
name = var_data['name']
|
|
482
|
+
value = var_data['value']
|
|
483
|
+
|
|
484
|
+
if name:
|
|
485
|
+
# Determine variable type based on name prefix
|
|
486
|
+
var_type = "local"
|
|
487
|
+
if name.startswith("@"):
|
|
488
|
+
var_type = "instance"
|
|
489
|
+
elif name.startswith("@@"):
|
|
490
|
+
var_type = "class"
|
|
491
|
+
elif name.startswith("$"):
|
|
492
|
+
var_type = "global"
|
|
493
|
+
|
|
494
|
+
variables.append({
|
|
495
|
+
"name": name,
|
|
496
|
+
"line_number": var_data['node'].start_point[0] + 1,
|
|
497
|
+
"value": value,
|
|
498
|
+
"type": var_type,
|
|
499
|
+
"context": None, # Placeholder
|
|
500
|
+
"class_context": None, # Placeholder
|
|
501
|
+
"lang": self.language_name,
|
|
502
|
+
"is_dependency": False,
|
|
503
|
+
})
|
|
504
|
+
|
|
505
|
+
return variables
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def pre_scan_ruby(files: list[Path], parser_wrapper) -> dict:
|
|
509
|
+
"""Scans Ruby files to create a map of class/method names to their file paths."""
|
|
510
|
+
imports_map = {}
|
|
511
|
+
query_str = """
|
|
512
|
+
(class
|
|
513
|
+
name: (constant) @name
|
|
514
|
+
)
|
|
515
|
+
(module
|
|
516
|
+
name: (constant) @name
|
|
517
|
+
)
|
|
518
|
+
(method
|
|
519
|
+
name: (identifier) @name
|
|
520
|
+
)
|
|
521
|
+
"""
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
for path in files:
|
|
525
|
+
try:
|
|
526
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
527
|
+
tree = parser_wrapper.parser.parse(bytes(f.read(), "utf8"))
|
|
528
|
+
|
|
529
|
+
for capture, _ in execute_query(parser_wrapper.language, query_str, tree.root_node):
|
|
530
|
+
name = capture.text.decode('utf-8')
|
|
531
|
+
if name not in imports_map:
|
|
532
|
+
imports_map[name] = []
|
|
533
|
+
imports_map[name].append(str(path.resolve()))
|
|
534
|
+
except Exception as e:
|
|
535
|
+
warning_logger(f"Tree-sitter pre-scan failed for {path}: {e}")
|
|
536
|
+
|
|
537
|
+
return imports_map
|