gitcortex_indexer/parser/
go.rs1use std::{
2 collections::HashMap,
3 path::{Path, PathBuf},
4};
5
6use gitcortex_core::{
7 error::{GitCortexError, Result},
8 graph::{Edge, Node, NodeId, NodeMetadata, Span},
9 schema::{EdgeKind, NodeKind, Visibility},
10};
11use tree_sitter::{Node as TsNode, Parser};
12
13use super::{LanguageParser, ParseResult};
14
15pub struct GoParser {
16 language: tree_sitter::Language,
17}
18
19impl GoParser {
20 pub fn new() -> Self {
21 Self {
22 language: tree_sitter_go::LANGUAGE.into(),
23 }
24 }
25}
26
27impl Default for GoParser {
28 fn default() -> Self {
29 Self::new()
30 }
31}
32
33impl LanguageParser for GoParser {
34 fn extensions(&self) -> &[&str] {
35 &["go"]
36 }
37
38 fn parse(&self, path: &Path, source: &str) -> Result<ParseResult> {
39 let mut parser = Parser::new();
40 parser
41 .set_language(&self.language)
42 .map_err(|e| GitCortexError::Parse {
43 file: path.to_owned(),
44 message: e.to_string(),
45 })?;
46
47 let tree = parser
48 .parse(source, None)
49 .ok_or_else(|| GitCortexError::Parse {
50 file: path.to_owned(),
51 message: "tree-sitter returned no parse tree".into(),
52 })?;
53
54 let mut visitor = FileVisitor::new(path, source);
55 visitor.collect_names(tree.root_node());
56 visitor.visit_source_file(tree.root_node());
57
58 Ok(ParseResult {
59 nodes: visitor.nodes,
60 edges: visitor.edges,
61 deferred_calls: visitor.deferred_calls,
62 deferred_uses: Vec::new(),
63 deferred_implements: Vec::new(),
64 deferred_imports: Vec::new(),
65 })
66 }
67}
68
69struct FileVisitor<'src> {
72 source: &'src [u8],
73 file: PathBuf,
74 nodes: Vec<Node>,
75 edges: Vec<Edge>,
76 type_index: HashMap<String, NodeId>,
78 fn_index: HashMap<String, NodeId>,
80 deferred_calls: Vec<(NodeId, String)>,
81}
82
83impl<'src> FileVisitor<'src> {
84 fn new(file: &Path, source: &'src str) -> Self {
85 Self {
86 source: source.as_bytes(),
87 file: file.to_owned(),
88 nodes: Vec::new(),
89 edges: Vec::new(),
90 type_index: HashMap::new(),
91 fn_index: HashMap::new(),
92 deferred_calls: Vec::new(),
93 }
94 }
95
96 fn text<'t>(&self, node: TsNode<'t>) -> &'src str {
97 node.utf8_text(self.source).unwrap_or("")
98 }
99
100 fn span(node: TsNode<'_>) -> Span {
101 Span {
102 start_line: node.start_position().row as u32 + 1,
103 end_line: node.end_position().row as u32 + 1,
104 }
105 }
106
107 fn visibility(name: &str) -> Visibility {
109 if name
110 .chars()
111 .next()
112 .map(|c| c.is_uppercase())
113 .unwrap_or(false)
114 {
115 Visibility::Pub
116 } else {
117 Visibility::Private
118 }
119 }
120
121 fn qualified(scope: &[String], name: &str) -> String {
122 if scope.is_empty() {
123 name.to_owned()
124 } else {
125 format!("{}.{name}", scope.join("."))
126 }
127 }
128
129 fn make_node(
130 &self,
131 id: NodeId,
132 kind: NodeKind,
133 name: String,
134 scope: &[String],
135 ts_node: TsNode<'_>,
136 ) -> Node {
137 Node {
138 id,
139 qualified_name: Self::qualified(scope, &name),
140 kind,
141 name: name.clone(),
142 file: self.file.clone(),
143 span: Self::span(ts_node),
144 metadata: NodeMetadata {
145 loc: (ts_node.end_position().row - ts_node.start_position().row + 1) as u32,
146 visibility: Self::visibility(&name),
147 is_async: false, is_unsafe: false,
149 ..Default::default()
150 },
151 }
152 }
153
154 fn collect_names(&mut self, node: TsNode<'_>) {
157 let mut cursor = node.walk();
158 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
159 for child in children {
160 match child.kind() {
161 "function_declaration" => {
162 if let Some(name_node) = child.child_by_field_name("name") {
163 let name = self.text(name_node).to_owned();
164 self.fn_index.entry(name).or_default();
165 }
166 }
167 "method_declaration" => {
168 if let Some(name_node) = child.child_by_field_name("name") {
169 let name = self.text(name_node).to_owned();
170 self.fn_index.entry(name).or_default();
171 }
172 }
173 "type_declaration" => {
174 self.collect_type_names(child);
175 }
176 _ => {}
177 }
178 }
179 }
180
181 fn collect_type_names(&mut self, decl: TsNode<'_>) {
182 let mut cursor = decl.walk();
183 for spec in decl.named_children(&mut cursor) {
184 if spec.kind() != "type_spec" {
185 continue;
186 }
187 if let Some(name_node) = spec.child_by_field_name("name") {
188 let name = self.text(name_node).to_owned();
189 if let Some(type_node) = spec.child_by_field_name("type") {
190 if matches!(type_node.kind(), "struct_type" | "interface_type") {
191 self.type_index.entry(name).or_default();
192 }
193 }
194 }
195 }
196 }
197
198 fn visit_source_file(&mut self, node: TsNode<'_>) {
201 let mut cursor = node.walk();
202 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
203 for child in children {
204 self.visit_top_level(child);
205 }
206 }
207
208 fn visit_top_level(&mut self, node: TsNode<'_>) {
209 match node.kind() {
210 "function_declaration" => self.visit_function(node, &[]),
211 "method_declaration" => self.visit_method(node),
212 "type_declaration" => self.visit_type_decl(node),
213 "const_declaration" => self.visit_const_decl(node),
214 _ => {}
215 }
216 }
217
218 fn visit_function(&mut self, node: TsNode<'_>, scope: &[String]) {
219 let Some(name_node) = node.child_by_field_name("name") else {
220 return;
221 };
222 let name = self.text(name_node).to_owned();
223 let id = self
224 .fn_index
225 .get(&name)
226 .cloned()
227 .unwrap_or_else(NodeId::new);
228 let graph_node = self.make_node(id.clone(), NodeKind::Function, name, scope, node);
229 self.nodes.push(graph_node);
230
231 if let Some(body) = node.child_by_field_name("body") {
232 self.collect_calls(body, &id);
233 }
234 }
235
236 fn visit_method(&mut self, node: TsNode<'_>) {
237 let Some(name_node) = node.child_by_field_name("name") else {
238 return;
239 };
240 let name = self.text(name_node).to_owned();
241
242 let receiver_type = self.receiver_type(node);
244 let scope: Vec<String> = receiver_type.into_iter().collect();
245
246 let container_id = scope.first().and_then(|t| self.type_index.get(t).cloned());
247 let id = self
248 .fn_index
249 .get(&name)
250 .cloned()
251 .unwrap_or_else(NodeId::new);
252 let graph_node = self.make_node(id.clone(), NodeKind::Method, name, &scope, node);
253
254 if let Some(cid) = container_id {
255 self.edges.push(Edge {
256 src: cid,
257 dst: id.clone(),
258 kind: EdgeKind::Contains,
259 });
260 }
261 self.nodes.push(graph_node);
262
263 if let Some(body) = node.child_by_field_name("body") {
264 self.collect_calls(body, &id);
265 }
266 }
267
268 fn receiver_type(&self, method_node: TsNode<'_>) -> Option<String> {
270 let recv = method_node.child_by_field_name("receiver")?;
271 let mut cursor = recv.walk();
273 for param in recv.named_children(&mut cursor) {
274 if param.kind() != "parameter_declaration" {
275 continue;
276 }
277 if let Some(type_node) = param.child_by_field_name("type") {
278 return match type_node.kind() {
279 "type_identifier" => Some(self.text(type_node).to_owned()),
280 "pointer_type" => {
281 let mut c = type_node.walk();
283 let result = type_node
284 .named_children(&mut c)
285 .find(|n| n.kind() == "type_identifier")
286 .map(|n| self.text(n).to_owned());
287 result
288 }
289 _ => None,
290 };
291 }
292 }
293 None
294 }
295
296 fn visit_type_decl(&mut self, decl: TsNode<'_>) {
297 let mut cursor = decl.walk();
298 let specs: Vec<TsNode<'_>> = decl.named_children(&mut cursor).collect();
299 for spec in specs {
300 if spec.kind() != "type_spec" {
301 continue;
302 }
303 let Some(name_node) = spec.child_by_field_name("name") else {
304 continue;
305 };
306 let name = self.text(name_node).to_owned();
307 let Some(type_node) = spec.child_by_field_name("type") else {
308 continue;
309 };
310
311 let kind = match type_node.kind() {
312 "struct_type" => NodeKind::Struct,
313 "interface_type" => NodeKind::Trait,
314 _ => {
315 let id = NodeId::new();
317 let graph_node = self.make_node(id, NodeKind::TypeAlias, name, &[], spec);
318 self.nodes.push(graph_node);
319 continue;
320 }
321 };
322
323 let id = self
324 .type_index
325 .get(&name)
326 .cloned()
327 .unwrap_or_else(NodeId::new);
328 let graph_node = self.make_node(id, kind, name, &[], spec);
329 self.nodes.push(graph_node);
330 }
331 }
332
333 fn visit_const_decl(&mut self, node: TsNode<'_>) {
334 let mut cursor = node.walk();
335 for spec in node.named_children(&mut cursor) {
336 if spec.kind() != "const_spec" {
337 continue;
338 }
339 let Some(name_node) = spec.child_by_field_name("name") else {
340 continue;
341 };
342 let name = self.text(name_node).to_owned();
343 let id = NodeId::new();
344 let graph_node = self.make_node(id, NodeKind::Constant, name, &[], spec);
345 self.nodes.push(graph_node);
346 }
347 }
348
349 fn collect_calls(&mut self, node: TsNode<'_>, caller_id: &NodeId) {
350 let mut cursor = node.walk();
351 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
352 for child in children {
353 if child.kind() == "call_expression" {
354 if let Some(callee) = self.callee_name(child) {
355 self.record_call(caller_id.clone(), callee);
356 }
357 if let Some(args) = child.child_by_field_name("arguments") {
358 self.collect_calls(args, caller_id);
359 }
360 } else {
361 self.collect_calls(child, caller_id);
362 }
363 }
364 }
365
366 fn callee_name(&self, call_expr: TsNode<'_>) -> Option<String> {
367 let func = call_expr.child_by_field_name("function")?;
368 match func.kind() {
369 "identifier" => Some(self.text(func).to_owned()),
370 "selector_expression" => func
371 .child_by_field_name("field")
372 .map(|n| self.text(n).to_owned()),
373 _ => None,
374 }
375 }
376
377 fn record_call(&mut self, caller_id: NodeId, callee_name: String) {
378 if callee_name.is_empty() {
379 return;
380 }
381 if let Some(callee_id) = self.fn_index.get(&callee_name).cloned() {
382 let edge = Edge {
383 src: caller_id,
384 dst: callee_id,
385 kind: EdgeKind::Calls,
386 };
387 if !self.edges.contains(&edge) {
388 self.edges.push(edge);
389 }
390 } else if !self
391 .deferred_calls
392 .iter()
393 .any(|(c, n)| c == &caller_id && n == &callee_name)
394 {
395 self.deferred_calls.push((caller_id, callee_name));
396 }
397 }
398}
399
400#[cfg(test)]
403mod tests {
404 use super::GoParser;
405 use crate::parser::LanguageParser;
406 use gitcortex_core::schema::{EdgeKind, NodeKind};
407 use std::path::Path;
408
409 fn parse(
410 src: &str,
411 ) -> (
412 Vec<gitcortex_core::graph::Node>,
413 Vec<gitcortex_core::graph::Edge>,
414 ) {
415 let r = GoParser::new().parse(Path::new("test.go"), src).unwrap();
416 (r.nodes, r.edges)
417 }
418
419 #[test]
420 fn parses_function() {
421 let src = "package main\nfunc Greet(name string) string { return name }";
422 let (nodes, _) = parse(src);
423 let fns: Vec<_> = nodes
424 .iter()
425 .filter(|n| n.kind == NodeKind::Function)
426 .collect();
427 assert_eq!(fns.len(), 1);
428 assert_eq!(fns[0].name, "Greet");
429 }
430
431 #[test]
432 fn parses_struct_and_method() {
433 let src = "package main\ntype Person struct { Name string }\nfunc (p *Person) Greet() string { return p.Name }";
434 let (nodes, edges) = parse(src);
435 let structs: Vec<_> = nodes
436 .iter()
437 .filter(|n| n.kind == NodeKind::Struct)
438 .collect();
439 let methods: Vec<_> = nodes
440 .iter()
441 .filter(|n| n.kind == NodeKind::Method)
442 .collect();
443 assert_eq!(structs.len(), 1);
444 assert_eq!(methods.len(), 1);
445 let contains: Vec<_> = edges
446 .iter()
447 .filter(|e| e.kind == EdgeKind::Contains)
448 .collect();
449 assert!(!contains.is_empty());
450 }
451
452 #[test]
453 fn parses_interface() {
454 let src = "package main\ntype Greeter interface { Greet() string }";
455 let (nodes, _) = parse(src);
456 let traits: Vec<_> = nodes.iter().filter(|n| n.kind == NodeKind::Trait).collect();
457 assert_eq!(traits.len(), 1);
458 assert_eq!(traits[0].name, "Greeter");
459 }
460
461 #[test]
462 fn go_visibility_is_uppercase() {
463 let src = "package main\nfunc Exported() {}\nfunc unexported() {}";
464 let (nodes, _) = parse(src);
465 use gitcortex_core::schema::Visibility;
466 let exp = nodes.iter().find(|n| n.name == "Exported").unwrap();
467 let unexp = nodes.iter().find(|n| n.name == "unexported").unwrap();
468 assert_eq!(exp.metadata.visibility, Visibility::Pub);
469 assert_eq!(unexp.metadata.visibility, Visibility::Private);
470 }
471
472 #[test]
473 fn detects_call_edges() {
474 let src = "package main\nfunc Caller() { Callee() }\nfunc Callee() {}";
475 let (_, edges) = parse(src);
476 let calls: Vec<_> = edges.iter().filter(|e| e.kind == EdgeKind::Calls).collect();
477 assert_eq!(calls.len(), 1);
478 }
479}