codegraphcontext-rust 0.5.5__cp310-cp310-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codegraphcontext/__init__.py +2 -0
- codegraphcontext/__main__.py +5 -0
- codegraphcontext/_cgc_rust.cp310-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,460 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any, Dict, Optional, Tuple
|
|
3
|
+
from codegraphcontext.utils.debug_log import warning_logger
|
|
4
|
+
|
|
5
|
+
# In Elixir's tree-sitter grammar, most constructs are `call` nodes
|
|
6
|
+
# with identifiers like `defmodule`, `def`, `defp`, etc.
|
|
7
|
+
|
|
8
|
+
ELIXIR_QUERIES = {
|
|
9
|
+
"modules": """
|
|
10
|
+
(call
|
|
11
|
+
target: (identifier) @keyword
|
|
12
|
+
(arguments (alias) @name)
|
|
13
|
+
(do_block)
|
|
14
|
+
) @module_node
|
|
15
|
+
""",
|
|
16
|
+
"functions": """
|
|
17
|
+
(call
|
|
18
|
+
target: (identifier) @keyword
|
|
19
|
+
(arguments
|
|
20
|
+
(call
|
|
21
|
+
target: (identifier) @name
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
) @function_node
|
|
25
|
+
""",
|
|
26
|
+
"imports": """
|
|
27
|
+
(call
|
|
28
|
+
target: (identifier) @keyword
|
|
29
|
+
(arguments (alias) @path)
|
|
30
|
+
) @import_node
|
|
31
|
+
""",
|
|
32
|
+
"calls": """
|
|
33
|
+
(call
|
|
34
|
+
target: (dot
|
|
35
|
+
left: (_) @receiver
|
|
36
|
+
right: (identifier) @name
|
|
37
|
+
)
|
|
38
|
+
(arguments) @args
|
|
39
|
+
) @call_node
|
|
40
|
+
""",
|
|
41
|
+
"simple_calls": """
|
|
42
|
+
(call
|
|
43
|
+
target: (identifier) @name
|
|
44
|
+
(arguments) @args
|
|
45
|
+
) @call_node
|
|
46
|
+
""",
|
|
47
|
+
"module_attributes": """
|
|
48
|
+
(unary_operator
|
|
49
|
+
operator: "@"
|
|
50
|
+
operand: (call
|
|
51
|
+
target: (identifier) @attr_name
|
|
52
|
+
(arguments (_) @attr_value)
|
|
53
|
+
)
|
|
54
|
+
) @attribute
|
|
55
|
+
""",
|
|
56
|
+
"comments": """
|
|
57
|
+
(comment) @comment
|
|
58
|
+
""",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Keywords that define modules/namespaces
|
|
62
|
+
MODULE_KEYWORDS = {"defmodule", "defprotocol", "defimpl"}
|
|
63
|
+
|
|
64
|
+
# Keywords that define functions
|
|
65
|
+
FUNCTION_KEYWORDS = {"def", "defp", "defmacro", "defmacrop", "defguard", "defguardp", "defdelegate"}
|
|
66
|
+
|
|
67
|
+
# Keywords that represent imports/dependencies
|
|
68
|
+
IMPORT_KEYWORDS = {"use", "import", "alias", "require"}
|
|
69
|
+
|
|
70
|
+
# Keywords to exclude from general call detection
|
|
71
|
+
ELIXIR_KEYWORDS = MODULE_KEYWORDS | FUNCTION_KEYWORDS | IMPORT_KEYWORDS | {
|
|
72
|
+
"quote", "unquote", "case", "cond", "if", "unless", "for", "with",
|
|
73
|
+
"try", "receive", "raise", "reraise", "throw", "super",
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ElixirTreeSitterParser:
|
|
78
|
+
"""An Elixir-specific parser using tree-sitter."""
|
|
79
|
+
|
|
80
|
+
def __init__(self, generic_parser_wrapper: Any):
|
|
81
|
+
self.generic_parser_wrapper = generic_parser_wrapper
|
|
82
|
+
self.language_name = "elixir"
|
|
83
|
+
self.language = generic_parser_wrapper.language
|
|
84
|
+
self.parser = generic_parser_wrapper.parser
|
|
85
|
+
self.index_source = False
|
|
86
|
+
|
|
87
|
+
def _get_node_text(self, node: Any) -> str:
|
|
88
|
+
return node.text.decode("utf-8")
|
|
89
|
+
|
|
90
|
+
def _get_parent_context(self, node: Any):
|
|
91
|
+
"""Find parent module or function context."""
|
|
92
|
+
curr = node.parent
|
|
93
|
+
while curr:
|
|
94
|
+
if curr.type == 'call':
|
|
95
|
+
# Check if it's a defmodule/def/defp etc
|
|
96
|
+
for child in curr.children:
|
|
97
|
+
if child.type == 'identifier':
|
|
98
|
+
keyword = self._get_node_text(child)
|
|
99
|
+
if keyword in MODULE_KEYWORDS:
|
|
100
|
+
# Get module name from arguments
|
|
101
|
+
for arg_child in curr.children:
|
|
102
|
+
if arg_child.type == 'arguments':
|
|
103
|
+
for ac in arg_child.children:
|
|
104
|
+
if ac.type == 'alias':
|
|
105
|
+
return self._get_node_text(ac), 'module', curr.start_point[0] + 1
|
|
106
|
+
elif keyword in FUNCTION_KEYWORDS:
|
|
107
|
+
for arg_child in curr.children:
|
|
108
|
+
if arg_child.type == 'arguments':
|
|
109
|
+
for ac in arg_child.children:
|
|
110
|
+
if ac.type == 'call':
|
|
111
|
+
name_node = ac.child_by_field_name('target')
|
|
112
|
+
if name_node:
|
|
113
|
+
return self._get_node_text(name_node), 'function', curr.start_point[0] + 1
|
|
114
|
+
break
|
|
115
|
+
curr = curr.parent
|
|
116
|
+
return None, None, None
|
|
117
|
+
|
|
118
|
+
def _enclosing_module_name(self, node: Any) -> Optional[str]:
|
|
119
|
+
"""Find the enclosing module name."""
|
|
120
|
+
curr = node.parent
|
|
121
|
+
while curr:
|
|
122
|
+
if curr.type == 'call':
|
|
123
|
+
for child in curr.children:
|
|
124
|
+
if child.type == 'identifier':
|
|
125
|
+
keyword = self._get_node_text(child)
|
|
126
|
+
if keyword in MODULE_KEYWORDS:
|
|
127
|
+
for arg_child in curr.children:
|
|
128
|
+
if arg_child.type == 'arguments':
|
|
129
|
+
for ac in arg_child.children:
|
|
130
|
+
if ac.type == 'alias':
|
|
131
|
+
return self._get_node_text(ac)
|
|
132
|
+
break
|
|
133
|
+
curr = curr.parent
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
def _calculate_complexity(self, node: Any) -> int:
|
|
137
|
+
"""Calculate cyclomatic complexity for Elixir constructs."""
|
|
138
|
+
complexity_keywords = {
|
|
139
|
+
"if", "unless", "case", "cond", "with", "for", "try",
|
|
140
|
+
"receive", "and", "or", "&&", "||", "when",
|
|
141
|
+
}
|
|
142
|
+
count = 1
|
|
143
|
+
|
|
144
|
+
def traverse(n):
|
|
145
|
+
nonlocal count
|
|
146
|
+
if n.type == 'identifier' and self._get_node_text(n) in complexity_keywords:
|
|
147
|
+
count += 1
|
|
148
|
+
elif n.type in ('binary_operator',):
|
|
149
|
+
op_text = self._get_node_text(n)
|
|
150
|
+
if '&&' in op_text or '||' in op_text or ' and ' in op_text or ' or ' in op_text:
|
|
151
|
+
count += 1
|
|
152
|
+
for child in n.children:
|
|
153
|
+
traverse(child)
|
|
154
|
+
|
|
155
|
+
traverse(node)
|
|
156
|
+
return count
|
|
157
|
+
|
|
158
|
+
def _get_docstring(self, node: Any) -> Optional[str]:
|
|
159
|
+
"""Extract @doc or @moduledoc attribute as docstring."""
|
|
160
|
+
prev = node.prev_sibling
|
|
161
|
+
while prev:
|
|
162
|
+
if prev.type == 'unary_operator':
|
|
163
|
+
text = self._get_node_text(prev)
|
|
164
|
+
if text.startswith('@doc') or text.startswith('@moduledoc'):
|
|
165
|
+
return text.strip()
|
|
166
|
+
elif prev.type == 'comment':
|
|
167
|
+
return self._get_node_text(prev).strip()
|
|
168
|
+
else:
|
|
169
|
+
break
|
|
170
|
+
prev = prev.prev_sibling
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
def parse(self, path: Path, is_dependency: bool = False, index_source: bool = False) -> Dict[str, Any]:
|
|
174
|
+
"""Parses an Elixir file and returns its structure."""
|
|
175
|
+
self.index_source = index_source
|
|
176
|
+
with open(path, "r", encoding="utf-8", errors="ignore") as f:
|
|
177
|
+
source_code = f.read()
|
|
178
|
+
|
|
179
|
+
tree = self.parser.parse(bytes(source_code, "utf8"))
|
|
180
|
+
root_node = tree.root_node
|
|
181
|
+
|
|
182
|
+
functions = self._find_functions(root_node)
|
|
183
|
+
modules = self._find_modules(root_node)
|
|
184
|
+
imports = self._find_imports(root_node)
|
|
185
|
+
function_calls = self._find_calls(root_node)
|
|
186
|
+
variables = [] # Elixir uses pattern matching, not traditional assignments
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
"path": str(path),
|
|
190
|
+
"functions": functions,
|
|
191
|
+
"classes": [], # Elixir doesn't have classes
|
|
192
|
+
"variables": variables,
|
|
193
|
+
"imports": imports,
|
|
194
|
+
"function_calls": function_calls,
|
|
195
|
+
"is_dependency": is_dependency,
|
|
196
|
+
"lang": self.language_name,
|
|
197
|
+
"modules": modules,
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
def _find_modules(self, root_node: Any) -> list[Dict[str, Any]]:
|
|
201
|
+
"""Find all defmodule, defprotocol, defimpl definitions."""
|
|
202
|
+
modules = []
|
|
203
|
+
self._find_modules_recursive(root_node, modules)
|
|
204
|
+
return modules
|
|
205
|
+
|
|
206
|
+
def _find_modules_recursive(self, node: Any, modules: list):
|
|
207
|
+
"""Recursively find module definitions."""
|
|
208
|
+
if node.type == 'call':
|
|
209
|
+
keyword = None
|
|
210
|
+
name = None
|
|
211
|
+
has_do_block = False
|
|
212
|
+
|
|
213
|
+
for child in node.children:
|
|
214
|
+
if child.type == 'identifier':
|
|
215
|
+
kw = self._get_node_text(child)
|
|
216
|
+
if kw in MODULE_KEYWORDS:
|
|
217
|
+
keyword = kw
|
|
218
|
+
elif child.type == 'arguments' and keyword:
|
|
219
|
+
for ac in child.children:
|
|
220
|
+
if ac.type == 'alias':
|
|
221
|
+
name = self._get_node_text(ac)
|
|
222
|
+
break
|
|
223
|
+
elif child.type == 'do_block':
|
|
224
|
+
has_do_block = True
|
|
225
|
+
|
|
226
|
+
if keyword and name and has_do_block:
|
|
227
|
+
module_data = {
|
|
228
|
+
"name": name,
|
|
229
|
+
"line_number": node.start_point[0] + 1,
|
|
230
|
+
"end_line": node.end_point[0] + 1,
|
|
231
|
+
"lang": self.language_name,
|
|
232
|
+
"is_dependency": False,
|
|
233
|
+
"type": keyword, # defmodule, defprotocol, defimpl
|
|
234
|
+
}
|
|
235
|
+
if self.index_source:
|
|
236
|
+
module_data["source"] = self._get_node_text(node)
|
|
237
|
+
modules.append(module_data)
|
|
238
|
+
|
|
239
|
+
for child in node.children:
|
|
240
|
+
self._find_modules_recursive(child, modules)
|
|
241
|
+
|
|
242
|
+
def _find_functions(self, root_node: Any) -> list[Dict[str, Any]]:
|
|
243
|
+
"""Find all def, defp, defmacro, etc. definitions."""
|
|
244
|
+
functions = []
|
|
245
|
+
self._find_functions_recursive(root_node, functions)
|
|
246
|
+
return functions
|
|
247
|
+
|
|
248
|
+
def _find_functions_recursive(self, node: Any, functions: list):
|
|
249
|
+
"""Recursively find function definitions."""
|
|
250
|
+
if node.type == 'call':
|
|
251
|
+
keyword = None
|
|
252
|
+
func_name = None
|
|
253
|
+
args = []
|
|
254
|
+
|
|
255
|
+
for child in node.children:
|
|
256
|
+
if child.type == 'identifier':
|
|
257
|
+
kw = self._get_node_text(child)
|
|
258
|
+
if kw in FUNCTION_KEYWORDS:
|
|
259
|
+
keyword = kw
|
|
260
|
+
elif child.type == 'arguments' and keyword:
|
|
261
|
+
for ac in child.children:
|
|
262
|
+
if ac.type == 'call':
|
|
263
|
+
# The function head: name(arg1, arg2)
|
|
264
|
+
target = ac.child_by_field_name('target')
|
|
265
|
+
if target:
|
|
266
|
+
func_name = self._get_node_text(target)
|
|
267
|
+
# Extract arguments
|
|
268
|
+
for acc in ac.children:
|
|
269
|
+
if acc.type == 'arguments':
|
|
270
|
+
for arg in acc.children:
|
|
271
|
+
if arg.type not in (',', '(', ')'):
|
|
272
|
+
args.append(self._get_node_text(arg))
|
|
273
|
+
elif ac.type == 'identifier' and not func_name:
|
|
274
|
+
# Zero-arity function: def my_func, do: ...
|
|
275
|
+
func_name = self._get_node_text(ac)
|
|
276
|
+
|
|
277
|
+
if keyword and func_name:
|
|
278
|
+
module_name = self._enclosing_module_name(node)
|
|
279
|
+
docstring = self._get_docstring(node)
|
|
280
|
+
|
|
281
|
+
func_data = {
|
|
282
|
+
"name": func_name,
|
|
283
|
+
"line_number": node.start_point[0] + 1,
|
|
284
|
+
"end_line": node.end_point[0] + 1,
|
|
285
|
+
"args": args,
|
|
286
|
+
"lang": self.language_name,
|
|
287
|
+
"is_dependency": False,
|
|
288
|
+
"visibility": "private" if keyword.endswith("p") else "public",
|
|
289
|
+
"type": keyword, # def, defp, defmacro, etc.
|
|
290
|
+
}
|
|
291
|
+
if self.index_source:
|
|
292
|
+
func_data["source"] = self._get_node_text(node)
|
|
293
|
+
func_data["docstring"] = docstring
|
|
294
|
+
if module_name:
|
|
295
|
+
func_data["context"] = module_name
|
|
296
|
+
func_data["context_type"] = "module"
|
|
297
|
+
func_data["class_context"] = module_name
|
|
298
|
+
|
|
299
|
+
functions.append(func_data)
|
|
300
|
+
|
|
301
|
+
for child in node.children:
|
|
302
|
+
self._find_functions_recursive(child, functions)
|
|
303
|
+
|
|
304
|
+
def _find_imports(self, root_node: Any) -> list[Dict[str, Any]]:
|
|
305
|
+
"""Find all use, import, alias, require statements."""
|
|
306
|
+
imports = []
|
|
307
|
+
self._find_imports_recursive(root_node, imports)
|
|
308
|
+
return imports
|
|
309
|
+
|
|
310
|
+
def _find_imports_recursive(self, node: Any, imports: list):
|
|
311
|
+
"""Recursively find import-like statements."""
|
|
312
|
+
if node.type == 'call':
|
|
313
|
+
keyword = None
|
|
314
|
+
path = None
|
|
315
|
+
|
|
316
|
+
for child in node.children:
|
|
317
|
+
if child.type == 'identifier':
|
|
318
|
+
kw = self._get_node_text(child)
|
|
319
|
+
if kw in IMPORT_KEYWORDS:
|
|
320
|
+
keyword = kw
|
|
321
|
+
elif child.type == 'arguments' and keyword:
|
|
322
|
+
for ac in child.children:
|
|
323
|
+
if ac.type == 'alias':
|
|
324
|
+
path = self._get_node_text(ac)
|
|
325
|
+
break
|
|
326
|
+
|
|
327
|
+
if keyword and path:
|
|
328
|
+
imports.append({
|
|
329
|
+
"name": path,
|
|
330
|
+
"full_import_name": f"{keyword} {path}",
|
|
331
|
+
"line_number": node.start_point[0] + 1,
|
|
332
|
+
"alias": path.split('.')[-1] if keyword == 'alias' else None,
|
|
333
|
+
"lang": self.language_name,
|
|
334
|
+
"is_dependency": False,
|
|
335
|
+
"import_type": keyword, # use, import, alias, require
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
for child in node.children:
|
|
339
|
+
self._find_imports_recursive(child, imports)
|
|
340
|
+
|
|
341
|
+
def _find_calls(self, root_node: Any) -> list[Dict[str, Any]]:
|
|
342
|
+
"""Find all function calls (excluding def/defmodule/etc keywords)."""
|
|
343
|
+
calls = []
|
|
344
|
+
self._find_calls_recursive(root_node, calls)
|
|
345
|
+
return calls
|
|
346
|
+
|
|
347
|
+
def _find_calls_recursive(self, node: Any, calls: list):
|
|
348
|
+
"""Recursively find function calls."""
|
|
349
|
+
if node.type == 'call':
|
|
350
|
+
target = None
|
|
351
|
+
receiver = None
|
|
352
|
+
name = None
|
|
353
|
+
args = []
|
|
354
|
+
|
|
355
|
+
for child in node.children:
|
|
356
|
+
if child.type == 'dot':
|
|
357
|
+
# Module.function() style call
|
|
358
|
+
left = child.child_by_field_name('left')
|
|
359
|
+
right = child.child_by_field_name('right')
|
|
360
|
+
if left:
|
|
361
|
+
receiver = self._get_node_text(left)
|
|
362
|
+
if right:
|
|
363
|
+
name = self._get_node_text(right)
|
|
364
|
+
elif child.type == 'identifier' and target is None:
|
|
365
|
+
target = self._get_node_text(child)
|
|
366
|
+
elif child.type == 'arguments':
|
|
367
|
+
for arg in child.children:
|
|
368
|
+
if arg.type not in (',', '(', ')'):
|
|
369
|
+
args.append(self._get_node_text(arg))
|
|
370
|
+
|
|
371
|
+
# Dot-call: Module.func(args)
|
|
372
|
+
if name and receiver:
|
|
373
|
+
full_name = f"{receiver}.{name}"
|
|
374
|
+
context_name, context_type, context_line = self._get_parent_context(node)
|
|
375
|
+
class_context = context_name if context_type == 'module' else None
|
|
376
|
+
if context_type == 'function':
|
|
377
|
+
class_context = self._enclosing_module_name(node)
|
|
378
|
+
|
|
379
|
+
calls.append({
|
|
380
|
+
"name": name,
|
|
381
|
+
"full_name": full_name,
|
|
382
|
+
"line_number": node.start_point[0] + 1,
|
|
383
|
+
"args": args,
|
|
384
|
+
"inferred_obj_type": receiver,
|
|
385
|
+
"context": (context_name, context_type, context_line),
|
|
386
|
+
"class_context": class_context,
|
|
387
|
+
"lang": self.language_name,
|
|
388
|
+
"is_dependency": False,
|
|
389
|
+
})
|
|
390
|
+
# Simple call: func(args), but skip Elixir keywords
|
|
391
|
+
elif target and target not in ELIXIR_KEYWORDS:
|
|
392
|
+
context_name, context_type, context_line = self._get_parent_context(node)
|
|
393
|
+
class_context = context_name if context_type == 'module' else None
|
|
394
|
+
if context_type == 'function':
|
|
395
|
+
class_context = self._enclosing_module_name(node)
|
|
396
|
+
|
|
397
|
+
calls.append({
|
|
398
|
+
"name": target,
|
|
399
|
+
"full_name": target,
|
|
400
|
+
"line_number": node.start_point[0] + 1,
|
|
401
|
+
"args": args,
|
|
402
|
+
"inferred_obj_type": None,
|
|
403
|
+
"context": (context_name, context_type, context_line),
|
|
404
|
+
"class_context": class_context,
|
|
405
|
+
"lang": self.language_name,
|
|
406
|
+
"is_dependency": False,
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
for child in node.children:
|
|
410
|
+
self._find_calls_recursive(child, calls)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def pre_scan_elixir(files: list[Path], parser_wrapper) -> dict:
|
|
414
|
+
"""Scans Elixir files to create a map of module/function names to their file paths."""
|
|
415
|
+
imports_map = {}
|
|
416
|
+
|
|
417
|
+
for path in files:
|
|
418
|
+
try:
|
|
419
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
420
|
+
source = f.read()
|
|
421
|
+
tree = parser_wrapper.parser.parse(bytes(source, "utf8"))
|
|
422
|
+
_pre_scan_recursive(tree.root_node, path, imports_map)
|
|
423
|
+
except Exception as e:
|
|
424
|
+
warning_logger(f"Tree-sitter pre-scan failed for {path}: {e}")
|
|
425
|
+
|
|
426
|
+
return imports_map
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _pre_scan_recursive(node, path: Path, imports_map: dict):
|
|
430
|
+
"""Recursively scan for module and function names."""
|
|
431
|
+
if node.type == 'call':
|
|
432
|
+
for child in node.children:
|
|
433
|
+
if child.type == 'identifier':
|
|
434
|
+
keyword = child.text.decode('utf-8')
|
|
435
|
+
if keyword in MODULE_KEYWORDS:
|
|
436
|
+
# Get module name
|
|
437
|
+
for sib in node.children:
|
|
438
|
+
if sib.type == 'arguments':
|
|
439
|
+
for ac in sib.children:
|
|
440
|
+
if ac.type == 'alias':
|
|
441
|
+
name = ac.text.decode('utf-8')
|
|
442
|
+
if name not in imports_map:
|
|
443
|
+
imports_map[name] = []
|
|
444
|
+
imports_map[name].append(str(path.resolve()))
|
|
445
|
+
elif keyword in FUNCTION_KEYWORDS:
|
|
446
|
+
# Get function name
|
|
447
|
+
for sib in node.children:
|
|
448
|
+
if sib.type == 'arguments':
|
|
449
|
+
for ac in sib.children:
|
|
450
|
+
if ac.type == 'call':
|
|
451
|
+
target = ac.child_by_field_name('target')
|
|
452
|
+
if target:
|
|
453
|
+
name = target.text.decode('utf-8')
|
|
454
|
+
if name not in imports_map:
|
|
455
|
+
imports_map[name] = []
|
|
456
|
+
imports_map[name].append(str(path.resolve()))
|
|
457
|
+
break
|
|
458
|
+
|
|
459
|
+
for child in node.children:
|
|
460
|
+
_pre_scan_recursive(child, path, imports_map)
|