Navegador

Blame History Raw 234 lines
1
"""
2
Go AST parser — extracts functions, methods, struct/interface types,
3
imports, and call edges from .go files using tree-sitter.
4
"""
5
6
import logging
7
from pathlib import Path
8
9
from navegador.graph.schema import EdgeType, NodeLabel
10
from navegador.graph.store import GraphStore
11
from navegador.ingestion.parser import LanguageParser
12
13
logger = logging.getLogger(__name__)
14
15
16
def _get_go_language():
17
try:
18
import tree_sitter_go as tsgo # type: ignore[import]
19
from tree_sitter import Language
20
21
return Language(tsgo.language())
22
except ImportError as e:
23
raise ImportError("Install tree-sitter-go: pip install tree-sitter-go") from e
24
25
26
def _node_text(node, source: bytes) -> str:
27
return source[node.start_byte : node.end_byte].decode("utf-8", errors="replace")
28
29
30
class GoParser(LanguageParser):
31
"""Parses Go source files into the navegador graph."""
32
33
def __init__(self) -> None:
34
from tree_sitter import Parser # type: ignore[import]
35
36
self._parser = Parser(_get_go_language())
37
38
def parse_file(self, path: Path, repo_root: Path, store: GraphStore) -> dict[str, int]:
39
source = path.read_bytes()
40
tree = self._parser.parse(source)
41
rel_path = str(path.relative_to(repo_root))
42
43
store.create_node(
44
NodeLabel.File,
45
{
46
"name": path.name,
47
"path": rel_path,
48
"language": "go",
49
"line_count": source.count(b"\n"),
50
},
51
)
52
53
stats = {"functions": 0, "classes": 0, "edges": 0}
54
self._walk(tree.root_node, source, rel_path, store, stats)
55
return stats
56
57
# ── AST walker ────────────────────────────────────────────────────────────
58
59
def _walk(self, node, source: bytes, file_path: str, store: GraphStore, stats: dict) -> None:
60
if node.type == "function_declaration":
61
self._handle_function(node, source, file_path, store, stats, receiver=None)
62
return
63
if node.type == "method_declaration":
64
self._handle_method(node, source, file_path, store, stats)
65
return
66
if node.type == "type_declaration":
67
self._handle_type(node, source, file_path, store, stats)
68
return
69
if node.type == "import_declaration":
70
self._handle_import(node, source, file_path, store, stats)
71
return
72
for child in node.children:
73
self._walk(child, source, file_path, store, stats)
74
75
# ── Handlers ──────────────────────────────────────────────────────────────
76
77
def _handle_function(
78
self,
79
node,
80
source: bytes,
81
file_path: str,
82
store: GraphStore,
83
stats: dict,
84
receiver: str | None,
85
) -> None:
86
name_node = node.child_by_field_name("name")
87
if not name_node:
88
return
89
name = _node_text(name_node, source)
90
label = NodeLabel.Method if receiver else NodeLabel.Function
91
92
store.create_node(
93
label,
94
{
95
"name": name,
96
"file_path": file_path,
97
"line_start": node.start_point[0] + 1,
98
"line_end": node.end_point[0] + 1,
99
"docstring": "",
100
"class_name": receiver or "",
101
},
102
)
103
104
container_label = NodeLabel.Class if receiver else NodeLabel.File
105
container_key = (
106
{"name": receiver, "file_path": file_path} if receiver else {"path": file_path}
107
)
108
store.create_edge(
109
container_label,
110
container_key,
111
EdgeType.CONTAINS,
112
label,
113
{"name": name, "file_path": file_path},
114
)
115
stats["functions"] += 1
116
stats["edges"] += 1
117
118
self._extract_calls(node, source, file_path, name, label, store, stats)
119
120
def _handle_method(
121
self, node, source: bytes, file_path: str, store: GraphStore, stats: dict
122
) -> None:
123
receiver_type = ""
124
recv_node = node.child_by_field_name("receiver")
125
if recv_node:
126
for child in recv_node.children:
127
if child.type == "parameter_declaration":
128
for c in child.children:
129
if c.type in ("type_identifier", "pointer_type"):
130
receiver_type = _node_text(c, source).lstrip("*").strip()
131
break
132
self._handle_function(node, source, file_path, store, stats, receiver=receiver_type or None)
133
134
def _handle_type(
135
self, node, source: bytes, file_path: str, store: GraphStore, stats: dict
136
) -> None:
137
for child in node.children:
138
if child.type != "type_spec":
139
continue
140
name_node = child.child_by_field_name("name")
141
type_node = child.child_by_field_name("type")
142
if not name_node or not type_node:
143
continue
144
if type_node.type not in ("struct_type", "interface_type"):
145
continue
146
name = _node_text(name_node, source)
147
kind = "struct" if type_node.type == "struct_type" else "interface"
148
store.create_node(
149
NodeLabel.Class,
150
{
151
"name": name,
152
"file_path": file_path,
153
"line_start": node.start_point[0] + 1,
154
"line_end": node.end_point[0] + 1,
155
"docstring": kind,
156
},
157
)
158
store.create_edge(
159
NodeLabel.File,
160
{"path": file_path},
161
EdgeType.CONTAINS,
162
NodeLabel.Class,
163
{"name": name, "file_path": file_path},
164
)
165
stats["classes"] += 1
166
stats["edges"] += 1
167
168
def _handle_import(
169
self, node, source: bytes, file_path: str, store: GraphStore, stats: dict
170
) -> None:
171
line_start = node.start_point[0] + 1
172
for child in node.children:
173
if child.type == "import_spec":
174
self._ingest_import_spec(child, source, file_path, line_start, store, stats)
175
elif child.type == "import_spec_list":
176
for spec in child.children:
177
if spec.type == "import_spec":
178
self._ingest_import_spec(spec, source, file_path, line_start, store, stats)
179
180
def _ingest_import_spec(
181
self, spec, source: bytes, file_path: str, line_start: int, store: GraphStore, stats: dict
182
) -> None:
183
path_node = spec.child_by_field_name("path")
184
if not path_node:
185
return
186
module = _node_text(path_node, source).strip('"')
187
store.create_node(
188
NodeLabel.Import,
189
{
190
"name": module,
191
"file_path": file_path,
192
"line_start": line_start,
193
"module": module,
194
},
195
)
196
store.create_edge(
197
NodeLabel.File,
198
{"path": file_path},
199
EdgeType.IMPORTS,
200
NodeLabel.Import,
201
{"name": module, "file_path": file_path},
202
)
203
stats["edges"] += 1
204
205
def _extract_calls(
206
self,
207
fn_node,
208
source: bytes,
209
file_path: str,
210
fn_name: str,
211
fn_label: str,
212
store: GraphStore,
213
stats: dict,
214
) -> None:
215
def walk(node):
216
if node.type == "call_expression":
217
func = node.child_by_field_name("function")
218
if func:
219
callee = _node_text(func, source).split(".")[-1]
220
store.create_edge(
221
fn_label,
222
{"name": fn_name, "file_path": file_path},
223
EdgeType.CALLS,
224
NodeLabel.Function,
225
{"name": callee, "file_path": file_path},
226
)
227
stats["edges"] += 1
228
for child in node.children:
229
walk(child)
230
231
body = fn_node.child_by_field_name("body")
232
if body:
233
walk(body)
234

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button