Skip to main content

reference_query/lang/go/
mod.rs

1//! Go plugin. Extracts `func` (free → function, with a receiver → method),
2//! `type … struct` → struct, and `type … interface` → trait (Go's interface is
3//! the same "named contract" concept). Methods are qualified by their receiver
4//! type (`Handle · Server`); interface method signatures by the interface.
5
6use tree_sitter::{Node, Parser};
7
8use crate::core::{Kind, Symbol};
9use crate::lang::LanguagePlugin;
10
11const LANGUAGE: &str = "go";
12
13pub struct Go;
14
15impl LanguagePlugin for Go {
16    fn language(&self) -> &'static str {
17        LANGUAGE
18    }
19
20    fn extensions(&self) -> &[&str] {
21        &["go"]
22    }
23
24    fn extract(&self, file: &str, source: &str) -> Vec<Symbol> {
25        let mut parser = Parser::new();
26        if parser
27            .set_language(&tree_sitter_go::LANGUAGE.into())
28            .is_err()
29        {
30            return Vec::new();
31        }
32        let Some(tree) = parser.parse(source, None) else {
33            return Vec::new();
34        };
35        let mut out = Vec::new();
36        let ctx = Ctx {
37            src: source.as_bytes(),
38            file,
39        };
40        ctx.walk(tree.root_node(), None, &mut out);
41        out
42    }
43}
44
45struct Ctx<'a> {
46    src: &'a [u8],
47    file: &'a str,
48}
49
50impl Ctx<'_> {
51    fn walk(&self, node: Node, parent: Option<&str>, out: &mut Vec<Symbol>) {
52        let mut cursor = node.walk();
53        for child in node.children(&mut cursor) {
54            match child.kind() {
55                "function_declaration" => {
56                    if let Some(name) = self.field_text(child, "name") {
57                        out.push(self.symbol(&name, Kind::Function, child, parent));
58                    }
59                }
60                "method_declaration" => {
61                    if let Some(name) = self.field_text(child, "name") {
62                        // qualify by the receiver type: `func (s *Server) Handle()`
63                        let recv = child
64                            .child_by_field_name("receiver")
65                            .and_then(|r| self.type_identifier(r));
66                        out.push(self.symbol(&name, Kind::Method, child, recv.as_deref()));
67                    }
68                }
69                "type_spec" => {
70                    if let Some(name) = self.field_text(child, "name") {
71                        match child.child_by_field_name("type").map(|t| t.kind()) {
72                            Some("struct_type") => {
73                                out.push(self.symbol(&name, Kind::Struct, child, parent));
74                            }
75                            Some("interface_type") => {
76                                out.push(self.symbol(&name, Kind::Trait, child, parent));
77                                // interface method signatures are methods of it
78                                self.walk(child, Some(&name), out);
79                            }
80                            _ => {}
81                        }
82                    }
83                }
84                // interface method signatures (node name varies by grammar version)
85                "method_spec" | "method_elem" => {
86                    if let Some(name) = self.field_text(child, "name") {
87                        out.push(self.symbol(&name, Kind::Method, child, parent));
88                    }
89                }
90                _ => self.walk(child, parent, out),
91            }
92        }
93    }
94
95    fn field_text(&self, node: Node, field: &str) -> Option<String> {
96        node.child_by_field_name(field)
97            .and_then(|n| n.utf8_text(self.src).ok())
98            .map(str::to_string)
99    }
100
101    /// The first `type_identifier` within `node` — used to pull the bare type
102    /// name out of a receiver like `(s *Server)` or `(s *Stack[T])`.
103    fn type_identifier(&self, node: Node) -> Option<String> {
104        if node.kind() == "type_identifier" {
105            return node.utf8_text(self.src).ok().map(str::to_string);
106        }
107        let mut cursor = node.walk();
108        for child in node.children(&mut cursor) {
109            if let Some(name) = self.type_identifier(child) {
110                return Some(name);
111            }
112        }
113        None
114    }
115
116    fn symbol(&self, name: &str, kind: Kind, node: Node, parent: Option<&str>) -> Symbol {
117        Symbol {
118            name: name.to_string(),
119            kind,
120            language: LANGUAGE.to_string(),
121            file: self.file.to_string(),
122            line: node.start_position().row as u32 + 1,
123            parent: parent.map(str::to_string),
124        }
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    fn extract(source: &str) -> Vec<Symbol> {
133        Go.extract("test.go", source)
134    }
135
136    fn find<'a>(syms: &'a [Symbol], name: &str) -> &'a Symbol {
137        syms.iter()
138            .find(|s| s.name == name)
139            .unwrap_or_else(|| panic!("no symbol named {name} in {syms:?}"))
140    }
141
142    #[test]
143    fn extracts_funcs_types_and_methods() {
144        let src = r#"
145package widget
146
147type Widget struct {
148	Size int
149}
150
151type Renderer interface {
152	Render() string
153}
154
155func (w *Widget) Resize(n int) {
156	w.Size = n
157}
158
159func Build() *Widget {
160	return &Widget{}
161}
162"#;
163        let syms = extract(src);
164
165        assert_eq!(find(&syms, "Widget").kind, Kind::Struct);
166        assert_eq!(find(&syms, "Renderer").kind, Kind::Trait);
167
168        // a free func vs a method qualified by its receiver type
169        let build = find(&syms, "Build");
170        assert_eq!(build.kind, Kind::Function);
171        assert_eq!(build.parent, None);
172
173        let resize = find(&syms, "Resize");
174        assert_eq!(resize.kind, Kind::Method);
175        assert_eq!(resize.parent.as_deref(), Some("Widget"));
176
177        // an interface method signature is a method of the interface
178        let render = find(&syms, "Render");
179        assert_eq!(render.kind, Kind::Method);
180        assert_eq!(render.parent.as_deref(), Some("Renderer"));
181
182        assert_eq!(build.language, "go");
183    }
184
185    #[test]
186    fn empty_and_unparseable_yield_no_symbols() {
187        assert!(extract("").is_empty());
188        assert!(extract("package x\n").is_empty());
189    }
190}