codegraph-nav 0.1.0__py3-none-any.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.
- codegraph_nav/__init__.py +194 -0
- codegraph_nav/ast_grep_analyzer.py +448 -0
- codegraph_nav/cli.py +223 -0
- codegraph_nav/code_navigator.py +1328 -0
- codegraph_nav/code_search.py +1009 -0
- codegraph_nav/colors.py +209 -0
- codegraph_nav/completions.py +354 -0
- codegraph_nav/dart_analyzer.py +301 -0
- codegraph_nav/dependency_graph.py +814 -0
- codegraph_nav/domain/__init__.py +20 -0
- codegraph_nav/domain/routes.py +337 -0
- codegraph_nav/domain/schemas.py +229 -0
- codegraph_nav/domain/tags.py +87 -0
- codegraph_nav/exporters.py +563 -0
- codegraph_nav/go_analyzer.py +273 -0
- codegraph_nav/graph/__init__.py +72 -0
- codegraph_nav/graph/builder.py +409 -0
- codegraph_nav/graph/communities.py +402 -0
- codegraph_nav/graph/flows.py +311 -0
- codegraph_nav/graph/query.py +380 -0
- codegraph_nav/graph/schema.py +266 -0
- codegraph_nav/graph/search.py +257 -0
- codegraph_nav/graph/store.py +517 -0
- codegraph_nav/hints.py +195 -0
- codegraph_nav/import_resolver.py +891 -0
- codegraph_nav/js_ts_analyzer.py +564 -0
- codegraph_nav/line_reader.py +664 -0
- codegraph_nav/mcp/__init__.py +39 -0
- codegraph_nav/mcp/__main__.py +5 -0
- codegraph_nav/mcp/server.py +2228 -0
- codegraph_nav/py.typed +2 -0
- codegraph_nav/ruby_analyzer.py +259 -0
- codegraph_nav/rust_analyzer.py +379 -0
- codegraph_nav/token_efficient_renderer.py +743 -0
- codegraph_nav/watcher.py +382 -0
- codegraph_nav-0.1.0.dist-info/METADATA +487 -0
- codegraph_nav-0.1.0.dist-info/RECORD +41 -0
- codegraph_nav-0.1.0.dist-info/WHEEL +5 -0
- codegraph_nav-0.1.0.dist-info/entry_points.txt +4 -0
- codegraph_nav-0.1.0.dist-info/licenses/LICENSE +21 -0
- codegraph_nav-0.1.0.dist-info/top_level.txt +1 -0
codegraph_nav/py.typed
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"""Ruby analyzer using tree-sitter for AST-based symbol extraction.
|
|
2
|
+
|
|
3
|
+
Falls back to regex-based GenericAnalyzer when tree-sitter is not installed.
|
|
4
|
+
|
|
5
|
+
Example:
|
|
6
|
+
>>> from codegraph_nav.ruby_analyzer import RubyAnalyzer
|
|
7
|
+
>>> source = '''
|
|
8
|
+
... def greet(name)
|
|
9
|
+
... "Hello, #{name}!"
|
|
10
|
+
... end
|
|
11
|
+
... '''
|
|
12
|
+
>>> analyzer = RubyAnalyzer('example.rb', source)
|
|
13
|
+
>>> symbols = analyzer.analyze()
|
|
14
|
+
>>> print(symbols[0].name)
|
|
15
|
+
'greet'
|
|
16
|
+
|
|
17
|
+
Installation:
|
|
18
|
+
To enable AST support, install with the 'ast' extra:
|
|
19
|
+
pip install codegraph-nav[ast]
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import sys
|
|
23
|
+
from typing import TYPE_CHECKING
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from tree_sitter import Node
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
import tree_sitter_ruby as ts_ruby
|
|
30
|
+
from tree_sitter import Language, Parser
|
|
31
|
+
|
|
32
|
+
TREE_SITTER_AVAILABLE = True
|
|
33
|
+
except ImportError:
|
|
34
|
+
TREE_SITTER_AVAILABLE = False
|
|
35
|
+
|
|
36
|
+
from .code_navigator import GenericAnalyzer, Symbol
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RubyAnalyzer:
|
|
40
|
+
"""Analyzes Ruby files using tree-sitter for accurate symbol extraction.
|
|
41
|
+
|
|
42
|
+
When tree-sitter is not available, automatically falls back to regex-based
|
|
43
|
+
GenericAnalyzer.
|
|
44
|
+
|
|
45
|
+
Attributes:
|
|
46
|
+
file_path: Path to the file being analyzed.
|
|
47
|
+
source: Source code content.
|
|
48
|
+
symbols: Extracted symbols.
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
>>> source = '''
|
|
52
|
+
... class User
|
|
53
|
+
... def initialize(name)
|
|
54
|
+
... @name = name
|
|
55
|
+
... end
|
|
56
|
+
...
|
|
57
|
+
... def greet
|
|
58
|
+
... "Hello, #{@name}!"
|
|
59
|
+
... end
|
|
60
|
+
... end
|
|
61
|
+
... '''
|
|
62
|
+
>>> analyzer = RubyAnalyzer('user.rb', source)
|
|
63
|
+
>>> symbols = analyzer.analyze()
|
|
64
|
+
>>> print([s.name for s in symbols])
|
|
65
|
+
['User', 'initialize', 'greet']
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(self, file_path: str, source: str):
|
|
69
|
+
self.file_path = file_path
|
|
70
|
+
self.source = source
|
|
71
|
+
self.lines = source.split("\n")
|
|
72
|
+
self.symbols: list[Symbol] = []
|
|
73
|
+
self._current_class: str | None = None
|
|
74
|
+
self._current_module: str | None = None
|
|
75
|
+
|
|
76
|
+
def analyze(self) -> list[Symbol]:
|
|
77
|
+
"""Parse and analyze the file.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
List of Symbol objects found in the file.
|
|
81
|
+
"""
|
|
82
|
+
if not TREE_SITTER_AVAILABLE:
|
|
83
|
+
fallback = GenericAnalyzer(self.file_path, self.source, "ruby")
|
|
84
|
+
return fallback.analyze()
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
parser = Parser(Language(ts_ruby.language()))
|
|
88
|
+
tree = parser.parse(bytes(self.source, "utf-8"))
|
|
89
|
+
self._visit_node(tree.root_node)
|
|
90
|
+
except Exception as e:
|
|
91
|
+
print(f"tree-sitter error in {self.file_path}: {e}", file=sys.stderr)
|
|
92
|
+
fallback = GenericAnalyzer(self.file_path, self.source, "ruby")
|
|
93
|
+
return fallback.analyze()
|
|
94
|
+
|
|
95
|
+
return self.symbols
|
|
96
|
+
|
|
97
|
+
def _visit_node(self, node: "Node") -> None:
|
|
98
|
+
"""Recursively visit AST nodes and extract symbols."""
|
|
99
|
+
node_type = node.type
|
|
100
|
+
|
|
101
|
+
if node_type == "method":
|
|
102
|
+
self._extract_method(node)
|
|
103
|
+
return # Don't visit children (nested defs handled separately)
|
|
104
|
+
elif node_type == "singleton_method":
|
|
105
|
+
self._extract_singleton_method(node)
|
|
106
|
+
return
|
|
107
|
+
elif node_type == "class":
|
|
108
|
+
self._extract_class(node)
|
|
109
|
+
return # Class body visited inside _extract_class
|
|
110
|
+
elif node_type == "module":
|
|
111
|
+
self._extract_module(node)
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
for child in node.children:
|
|
115
|
+
self._visit_node(child)
|
|
116
|
+
|
|
117
|
+
def _get_node_text(self, node: "Node") -> str:
|
|
118
|
+
return self.source[node.start_byte : node.end_byte]
|
|
119
|
+
|
|
120
|
+
def _get_child_by_type(self, node: "Node", type_name: str) -> "Node | None":
|
|
121
|
+
for child in node.children:
|
|
122
|
+
if child.type == type_name:
|
|
123
|
+
return child
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
def _extract_method(self, node: "Node") -> None:
|
|
127
|
+
"""Extract a method definition."""
|
|
128
|
+
name_node = self._get_child_by_type(node, "identifier")
|
|
129
|
+
if not name_node:
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
name = self._get_node_text(name_node)
|
|
133
|
+
|
|
134
|
+
# Get parameters
|
|
135
|
+
params = ""
|
|
136
|
+
params_node = self._get_child_by_type(node, "method_parameters")
|
|
137
|
+
if params_node:
|
|
138
|
+
params = self._get_node_text(params_node)
|
|
139
|
+
|
|
140
|
+
# Determine type based on context
|
|
141
|
+
parent = self._current_class or self._current_module
|
|
142
|
+
symbol_type = "method" if parent else "function"
|
|
143
|
+
|
|
144
|
+
signature = f"def {name}{params}"
|
|
145
|
+
|
|
146
|
+
self.symbols.append(
|
|
147
|
+
Symbol(
|
|
148
|
+
name=name,
|
|
149
|
+
type=symbol_type,
|
|
150
|
+
file_path=self.file_path,
|
|
151
|
+
line_start=node.start_point[0] + 1,
|
|
152
|
+
line_end=node.end_point[0] + 1,
|
|
153
|
+
signature=signature[:100],
|
|
154
|
+
parent=parent,
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
def _extract_singleton_method(self, node: "Node") -> None:
|
|
159
|
+
"""Extract a singleton (class-level) method like def self.method_name."""
|
|
160
|
+
# Get the method name
|
|
161
|
+
name_node = self._get_child_by_type(node, "identifier")
|
|
162
|
+
if not name_node:
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
name = self._get_node_text(name_node)
|
|
166
|
+
|
|
167
|
+
# Get parameters
|
|
168
|
+
params = ""
|
|
169
|
+
params_node = self._get_child_by_type(node, "method_parameters")
|
|
170
|
+
if params_node:
|
|
171
|
+
params = self._get_node_text(params_node)
|
|
172
|
+
|
|
173
|
+
parent = self._current_class or self._current_module
|
|
174
|
+
signature = f"def self.{name}{params}"
|
|
175
|
+
|
|
176
|
+
self.symbols.append(
|
|
177
|
+
Symbol(
|
|
178
|
+
name=name,
|
|
179
|
+
type="method",
|
|
180
|
+
file_path=self.file_path,
|
|
181
|
+
line_start=node.start_point[0] + 1,
|
|
182
|
+
line_end=node.end_point[0] + 1,
|
|
183
|
+
signature=signature[:100],
|
|
184
|
+
parent=parent,
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def _extract_class(self, node: "Node") -> None:
|
|
189
|
+
"""Extract a class declaration and visit its body."""
|
|
190
|
+
# Get class name (constant node)
|
|
191
|
+
name_node = self._get_child_by_type(node, "constant")
|
|
192
|
+
if not name_node:
|
|
193
|
+
# Try scope_resolution for nested classes like A::B
|
|
194
|
+
name_node = self._get_child_by_type(node, "scope_resolution")
|
|
195
|
+
if not name_node:
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
name = self._get_node_text(name_node)
|
|
199
|
+
|
|
200
|
+
# Get superclass
|
|
201
|
+
superclass = ""
|
|
202
|
+
sup_node = self._get_child_by_type(node, "superclass")
|
|
203
|
+
if sup_node:
|
|
204
|
+
for child in sup_node.children:
|
|
205
|
+
if child.type in ("constant", "scope_resolution"):
|
|
206
|
+
superclass = f" < {self._get_node_text(child)}"
|
|
207
|
+
break
|
|
208
|
+
|
|
209
|
+
signature = f"class {name}{superclass}"
|
|
210
|
+
|
|
211
|
+
self.symbols.append(
|
|
212
|
+
Symbol(
|
|
213
|
+
name=name,
|
|
214
|
+
type="class",
|
|
215
|
+
file_path=self.file_path,
|
|
216
|
+
line_start=node.start_point[0] + 1,
|
|
217
|
+
line_end=node.end_point[0] + 1,
|
|
218
|
+
signature=signature[:100],
|
|
219
|
+
)
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Visit body with class context
|
|
223
|
+
old_class = self._current_class
|
|
224
|
+
self._current_class = name
|
|
225
|
+
body = self._get_child_by_type(node, "body_statement")
|
|
226
|
+
if body:
|
|
227
|
+
for child in body.children:
|
|
228
|
+
self._visit_node(child)
|
|
229
|
+
self._current_class = old_class
|
|
230
|
+
|
|
231
|
+
def _extract_module(self, node: "Node") -> None:
|
|
232
|
+
"""Extract a module declaration and visit its body."""
|
|
233
|
+
name_node = self._get_child_by_type(node, "constant")
|
|
234
|
+
if not name_node:
|
|
235
|
+
return
|
|
236
|
+
|
|
237
|
+
name = self._get_node_text(name_node)
|
|
238
|
+
|
|
239
|
+
signature = f"module {name}"
|
|
240
|
+
|
|
241
|
+
self.symbols.append(
|
|
242
|
+
Symbol(
|
|
243
|
+
name=name,
|
|
244
|
+
type="module",
|
|
245
|
+
file_path=self.file_path,
|
|
246
|
+
line_start=node.start_point[0] + 1,
|
|
247
|
+
line_end=node.end_point[0] + 1,
|
|
248
|
+
signature=signature[:100],
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# Visit body with module context
|
|
253
|
+
old_module = self._current_module
|
|
254
|
+
self._current_module = name
|
|
255
|
+
body = self._get_child_by_type(node, "body_statement")
|
|
256
|
+
if body:
|
|
257
|
+
for child in body.children:
|
|
258
|
+
self._visit_node(child)
|
|
259
|
+
self._current_module = old_module
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
"""Rust analyzer using tree-sitter for AST-based symbol extraction.
|
|
2
|
+
|
|
3
|
+
Falls back to regex-based GenericAnalyzer when tree-sitter is not installed.
|
|
4
|
+
|
|
5
|
+
Example:
|
|
6
|
+
>>> from codegraph_nav.rust_analyzer import RustAnalyzer
|
|
7
|
+
>>> source = '''
|
|
8
|
+
... pub fn greet(name: &str) -> String {
|
|
9
|
+
... format!("Hello, {}!", name)
|
|
10
|
+
... }
|
|
11
|
+
... '''
|
|
12
|
+
>>> analyzer = RustAnalyzer('example.rs', source)
|
|
13
|
+
>>> symbols = analyzer.analyze()
|
|
14
|
+
>>> print(symbols[0].name)
|
|
15
|
+
'greet'
|
|
16
|
+
|
|
17
|
+
Installation:
|
|
18
|
+
To enable AST support, install with the 'ast' extra:
|
|
19
|
+
pip install codegraph-nav[ast]
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import sys
|
|
23
|
+
from typing import TYPE_CHECKING
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from tree_sitter import Node
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
import tree_sitter_rust as ts_rust
|
|
30
|
+
from tree_sitter import Language, Parser
|
|
31
|
+
|
|
32
|
+
TREE_SITTER_AVAILABLE = True
|
|
33
|
+
except ImportError:
|
|
34
|
+
TREE_SITTER_AVAILABLE = False
|
|
35
|
+
|
|
36
|
+
from .code_navigator import GenericAnalyzer, Symbol
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RustAnalyzer:
|
|
40
|
+
"""Analyzes Rust files using tree-sitter for accurate symbol extraction.
|
|
41
|
+
|
|
42
|
+
When tree-sitter is not available, automatically falls back to regex-based
|
|
43
|
+
GenericAnalyzer.
|
|
44
|
+
|
|
45
|
+
Attributes:
|
|
46
|
+
file_path: Path to the file being analyzed.
|
|
47
|
+
source: Source code content.
|
|
48
|
+
symbols: Extracted symbols.
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
>>> source = '''
|
|
52
|
+
... pub struct User {
|
|
53
|
+
... name: String,
|
|
54
|
+
... }
|
|
55
|
+
...
|
|
56
|
+
... impl User {
|
|
57
|
+
... pub fn new(name: String) -> Self {
|
|
58
|
+
... User { name }
|
|
59
|
+
... }
|
|
60
|
+
... }
|
|
61
|
+
... '''
|
|
62
|
+
>>> analyzer = RustAnalyzer('user.rs', source)
|
|
63
|
+
>>> symbols = analyzer.analyze()
|
|
64
|
+
>>> print([s.name for s in symbols])
|
|
65
|
+
['User', 'User', 'new']
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(self, file_path: str, source: str):
|
|
69
|
+
self.file_path = file_path
|
|
70
|
+
self.source = source
|
|
71
|
+
self.lines = source.split("\n")
|
|
72
|
+
self.symbols: list[Symbol] = []
|
|
73
|
+
self._current_impl: str | None = None
|
|
74
|
+
self._current_trait_impl: str | None = None
|
|
75
|
+
|
|
76
|
+
def analyze(self) -> list[Symbol]:
|
|
77
|
+
"""Parse and analyze the file.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
List of Symbol objects found in the file.
|
|
81
|
+
"""
|
|
82
|
+
if not TREE_SITTER_AVAILABLE:
|
|
83
|
+
fallback = GenericAnalyzer(self.file_path, self.source, "rust")
|
|
84
|
+
return fallback.analyze()
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
parser = Parser(Language(ts_rust.language()))
|
|
88
|
+
tree = parser.parse(bytes(self.source, "utf-8"))
|
|
89
|
+
self._visit_node(tree.root_node)
|
|
90
|
+
except Exception as e:
|
|
91
|
+
print(f"tree-sitter error in {self.file_path}: {e}", file=sys.stderr)
|
|
92
|
+
fallback = GenericAnalyzer(self.file_path, self.source, "rust")
|
|
93
|
+
return fallback.analyze()
|
|
94
|
+
|
|
95
|
+
return self.symbols
|
|
96
|
+
|
|
97
|
+
def _visit_node(self, node: "Node") -> None:
|
|
98
|
+
"""Recursively visit AST nodes and extract symbols."""
|
|
99
|
+
node_type = node.type
|
|
100
|
+
|
|
101
|
+
if node_type == "function_item":
|
|
102
|
+
self._extract_function(node)
|
|
103
|
+
elif node_type == "struct_item":
|
|
104
|
+
self._extract_struct(node)
|
|
105
|
+
elif node_type == "enum_item":
|
|
106
|
+
self._extract_enum(node)
|
|
107
|
+
elif node_type == "trait_item":
|
|
108
|
+
self._extract_trait(node)
|
|
109
|
+
elif node_type == "impl_item":
|
|
110
|
+
self._extract_impl(node)
|
|
111
|
+
return # Body visited inside _extract_impl
|
|
112
|
+
elif node_type == "type_item":
|
|
113
|
+
self._extract_type_alias(node)
|
|
114
|
+
elif node_type == "const_item":
|
|
115
|
+
self._extract_const(node)
|
|
116
|
+
elif node_type == "mod_item":
|
|
117
|
+
self._extract_mod(node)
|
|
118
|
+
return # Body visited inside _extract_mod
|
|
119
|
+
|
|
120
|
+
for child in node.children:
|
|
121
|
+
self._visit_node(child)
|
|
122
|
+
|
|
123
|
+
def _get_node_text(self, node: "Node") -> str:
|
|
124
|
+
return self.source[node.start_byte : node.end_byte]
|
|
125
|
+
|
|
126
|
+
def _get_child_by_type(self, node: "Node", type_name: str) -> "Node | None":
|
|
127
|
+
for child in node.children:
|
|
128
|
+
if child.type == type_name:
|
|
129
|
+
return child
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
def _has_visibility(self, node: "Node") -> bool:
|
|
133
|
+
return self._get_child_by_type(node, "visibility_modifier") is not None
|
|
134
|
+
|
|
135
|
+
def _is_async(self, node: "Node") -> bool:
|
|
136
|
+
mods = self._get_child_by_type(node, "function_modifiers")
|
|
137
|
+
if mods:
|
|
138
|
+
for child in mods.children:
|
|
139
|
+
if child.type == "async":
|
|
140
|
+
return True
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
def _get_type_params(self, node: "Node") -> str:
|
|
144
|
+
tp = self._get_child_by_type(node, "type_parameters")
|
|
145
|
+
if tp:
|
|
146
|
+
return self._get_node_text(tp)
|
|
147
|
+
return ""
|
|
148
|
+
|
|
149
|
+
def _extract_function(self, node: "Node") -> None:
|
|
150
|
+
"""Extract a function/method item."""
|
|
151
|
+
name_node = self._get_child_by_type(node, "identifier")
|
|
152
|
+
if not name_node:
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
name = self._get_node_text(name_node)
|
|
156
|
+
|
|
157
|
+
# Visibility
|
|
158
|
+
pub = "pub " if self._has_visibility(node) else ""
|
|
159
|
+
async_prefix = "async " if self._is_async(node) else ""
|
|
160
|
+
type_params = self._get_type_params(node)
|
|
161
|
+
|
|
162
|
+
# Parameters
|
|
163
|
+
params = ""
|
|
164
|
+
params_node = self._get_child_by_type(node, "parameters")
|
|
165
|
+
if params_node:
|
|
166
|
+
params = self._get_node_text(params_node)
|
|
167
|
+
|
|
168
|
+
# Return type
|
|
169
|
+
ret = ""
|
|
170
|
+
for i, child in enumerate(node.children):
|
|
171
|
+
if child.type == "->":
|
|
172
|
+
remaining = [c for c in node.children[i + 1 :] if c.type != "block"]
|
|
173
|
+
if remaining:
|
|
174
|
+
ret = " -> " + self._get_node_text(remaining[0])
|
|
175
|
+
break
|
|
176
|
+
|
|
177
|
+
# Determine type
|
|
178
|
+
parent = self._current_impl
|
|
179
|
+
symbol_type = "method" if parent else "function"
|
|
180
|
+
|
|
181
|
+
signature = f"{pub}{async_prefix}fn {name}{type_params}{params}{ret}"
|
|
182
|
+
|
|
183
|
+
self.symbols.append(
|
|
184
|
+
Symbol(
|
|
185
|
+
name=name,
|
|
186
|
+
type=symbol_type,
|
|
187
|
+
file_path=self.file_path,
|
|
188
|
+
line_start=node.start_point[0] + 1,
|
|
189
|
+
line_end=node.end_point[0] + 1,
|
|
190
|
+
signature=signature[:100],
|
|
191
|
+
parent=parent,
|
|
192
|
+
)
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def _extract_struct(self, node: "Node") -> None:
|
|
196
|
+
"""Extract a struct definition."""
|
|
197
|
+
name_node = self._get_child_by_type(node, "type_identifier")
|
|
198
|
+
if not name_node:
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
name = self._get_node_text(name_node)
|
|
202
|
+
pub = "pub " if self._has_visibility(node) else ""
|
|
203
|
+
type_params = self._get_type_params(node)
|
|
204
|
+
signature = f"{pub}struct {name}{type_params}"
|
|
205
|
+
|
|
206
|
+
self.symbols.append(
|
|
207
|
+
Symbol(
|
|
208
|
+
name=name,
|
|
209
|
+
type="struct",
|
|
210
|
+
file_path=self.file_path,
|
|
211
|
+
line_start=node.start_point[0] + 1,
|
|
212
|
+
line_end=node.end_point[0] + 1,
|
|
213
|
+
signature=signature[:100],
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
def _extract_enum(self, node: "Node") -> None:
|
|
218
|
+
"""Extract an enum definition."""
|
|
219
|
+
name_node = self._get_child_by_type(node, "type_identifier")
|
|
220
|
+
if not name_node:
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
name = self._get_node_text(name_node)
|
|
224
|
+
pub = "pub " if self._has_visibility(node) else ""
|
|
225
|
+
signature = f"{pub}enum {name}"
|
|
226
|
+
|
|
227
|
+
self.symbols.append(
|
|
228
|
+
Symbol(
|
|
229
|
+
name=name,
|
|
230
|
+
type="enum",
|
|
231
|
+
file_path=self.file_path,
|
|
232
|
+
line_start=node.start_point[0] + 1,
|
|
233
|
+
line_end=node.end_point[0] + 1,
|
|
234
|
+
signature=signature[:100],
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def _extract_trait(self, node: "Node") -> None:
|
|
239
|
+
"""Extract a trait definition."""
|
|
240
|
+
name_node = self._get_child_by_type(node, "type_identifier")
|
|
241
|
+
if not name_node:
|
|
242
|
+
return
|
|
243
|
+
|
|
244
|
+
name = self._get_node_text(name_node)
|
|
245
|
+
pub = "pub " if self._has_visibility(node) else ""
|
|
246
|
+
type_params = self._get_type_params(node)
|
|
247
|
+
signature = f"{pub}trait {name}{type_params}"
|
|
248
|
+
|
|
249
|
+
self.symbols.append(
|
|
250
|
+
Symbol(
|
|
251
|
+
name=name,
|
|
252
|
+
type="trait",
|
|
253
|
+
file_path=self.file_path,
|
|
254
|
+
line_start=node.start_point[0] + 1,
|
|
255
|
+
line_end=node.end_point[0] + 1,
|
|
256
|
+
signature=signature[:100],
|
|
257
|
+
)
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
def _extract_impl(self, node: "Node") -> None:
|
|
261
|
+
"""Extract an impl block and visit its methods."""
|
|
262
|
+
# Get the type being implemented
|
|
263
|
+
# For "impl Type", get Type
|
|
264
|
+
# For "impl Trait for Type", get Type (the target)
|
|
265
|
+
impl_type = None
|
|
266
|
+
trait_name = None
|
|
267
|
+
|
|
268
|
+
type_ids = [c for c in node.children if c.type == "type_identifier"]
|
|
269
|
+
scoped_ids = [c for c in node.children if c.type == "scoped_type_identifier"]
|
|
270
|
+
has_for = any(c.type == "for" for c in node.children)
|
|
271
|
+
|
|
272
|
+
if has_for:
|
|
273
|
+
# impl Trait for Type
|
|
274
|
+
if len(type_ids) >= 1:
|
|
275
|
+
impl_type = self._get_node_text(type_ids[-1])
|
|
276
|
+
if scoped_ids:
|
|
277
|
+
trait_name = self._get_node_text(scoped_ids[0])
|
|
278
|
+
elif len(type_ids) >= 2:
|
|
279
|
+
trait_name = self._get_node_text(type_ids[0])
|
|
280
|
+
else:
|
|
281
|
+
# impl Type
|
|
282
|
+
if type_ids:
|
|
283
|
+
impl_type = self._get_node_text(type_ids[0])
|
|
284
|
+
|
|
285
|
+
if impl_type:
|
|
286
|
+
type_params = self._get_type_params(node)
|
|
287
|
+
if trait_name:
|
|
288
|
+
sig = f"impl {trait_name} for {impl_type}{type_params}"
|
|
289
|
+
else:
|
|
290
|
+
sig = f"impl {impl_type}{type_params}"
|
|
291
|
+
|
|
292
|
+
self.symbols.append(
|
|
293
|
+
Symbol(
|
|
294
|
+
name=impl_type,
|
|
295
|
+
type="impl",
|
|
296
|
+
file_path=self.file_path,
|
|
297
|
+
line_start=node.start_point[0] + 1,
|
|
298
|
+
line_end=node.end_point[0] + 1,
|
|
299
|
+
signature=sig[:100],
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# Visit body with impl context
|
|
304
|
+
old_impl = self._current_impl
|
|
305
|
+
self._current_impl = impl_type
|
|
306
|
+
decl_list = self._get_child_by_type(node, "declaration_list")
|
|
307
|
+
if decl_list:
|
|
308
|
+
for child in decl_list.children:
|
|
309
|
+
self._visit_node(child)
|
|
310
|
+
self._current_impl = old_impl
|
|
311
|
+
|
|
312
|
+
def _extract_type_alias(self, node: "Node") -> None:
|
|
313
|
+
"""Extract a type alias."""
|
|
314
|
+
name_node = self._get_child_by_type(node, "type_identifier")
|
|
315
|
+
if not name_node:
|
|
316
|
+
return
|
|
317
|
+
|
|
318
|
+
name = self._get_node_text(name_node)
|
|
319
|
+
pub = "pub " if self._has_visibility(node) else ""
|
|
320
|
+
type_params = self._get_type_params(node)
|
|
321
|
+
signature = f"{pub}type {name}{type_params}"
|
|
322
|
+
|
|
323
|
+
self.symbols.append(
|
|
324
|
+
Symbol(
|
|
325
|
+
name=name,
|
|
326
|
+
type="type",
|
|
327
|
+
file_path=self.file_path,
|
|
328
|
+
line_start=node.start_point[0] + 1,
|
|
329
|
+
line_end=node.end_point[0] + 1,
|
|
330
|
+
signature=signature[:100],
|
|
331
|
+
)
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
def _extract_const(self, node: "Node") -> None:
|
|
335
|
+
"""Extract a const item."""
|
|
336
|
+
name_node = self._get_child_by_type(node, "identifier")
|
|
337
|
+
if not name_node:
|
|
338
|
+
return
|
|
339
|
+
|
|
340
|
+
name = self._get_node_text(name_node)
|
|
341
|
+
pub = "pub " if self._has_visibility(node) else ""
|
|
342
|
+
signature = f"{pub}const {name}"
|
|
343
|
+
|
|
344
|
+
self.symbols.append(
|
|
345
|
+
Symbol(
|
|
346
|
+
name=name,
|
|
347
|
+
type="const",
|
|
348
|
+
file_path=self.file_path,
|
|
349
|
+
line_start=node.start_point[0] + 1,
|
|
350
|
+
line_end=node.end_point[0] + 1,
|
|
351
|
+
signature=signature[:100],
|
|
352
|
+
)
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
def _extract_mod(self, node: "Node") -> None:
|
|
356
|
+
"""Extract a module declaration and visit its body."""
|
|
357
|
+
name_node = self._get_child_by_type(node, "identifier")
|
|
358
|
+
if not name_node:
|
|
359
|
+
return
|
|
360
|
+
|
|
361
|
+
name = self._get_node_text(name_node)
|
|
362
|
+
signature = f"mod {name}"
|
|
363
|
+
|
|
364
|
+
self.symbols.append(
|
|
365
|
+
Symbol(
|
|
366
|
+
name=name,
|
|
367
|
+
type="module",
|
|
368
|
+
file_path=self.file_path,
|
|
369
|
+
line_start=node.start_point[0] + 1,
|
|
370
|
+
line_end=node.end_point[0] + 1,
|
|
371
|
+
signature=signature[:100],
|
|
372
|
+
)
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
# Visit body
|
|
376
|
+
decl_list = self._get_child_by_type(node, "declaration_list")
|
|
377
|
+
if decl_list:
|
|
378
|
+
for child in decl_list.children:
|
|
379
|
+
self._visit_node(child)
|