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;
7
8use crate::core::{Kind, Symbol};
9use crate::lang::{Ctx, LanguagePlugin, extract_with};
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        extract_with(
26            LANGUAGE,
27            tree_sitter_go::LANGUAGE.into(),
28            file,
29            source,
30            |ctx, root, out| walk(ctx, root, None, out),
31        )
32    }
33}
34
35fn walk(ctx: &Ctx, node: Node, parent: Option<&str>, out: &mut Vec<Symbol>) {
36    let mut cursor = node.walk();
37    for child in node.children(&mut cursor) {
38        match child.kind() {
39            "function_declaration" => {
40                if let Some(name) = ctx.field_text(child, "name") {
41                    push(ctx, out, &name, Kind::Function, child, parent);
42                }
43            }
44            "method_declaration" => {
45                if let Some(name) = ctx.field_text(child, "name") {
46                    // qualify by the receiver type: `func (s *Server) Handle()`
47                    let recv = child
48                        .child_by_field_name("receiver")
49                        .and_then(|r| type_identifier(ctx, r));
50                    push(ctx, out, &name, Kind::Method, child, recv.as_deref());
51                }
52            }
53            "type_spec" => {
54                if let Some(name) = ctx.field_text(child, "name") {
55                    match child.child_by_field_name("type").map(|t| t.kind()) {
56                        Some("struct_type") => {
57                            push(ctx, out, &name, Kind::Struct, child, parent);
58                        }
59                        Some("interface_type") => {
60                            push(ctx, out, &name, Kind::Trait, child, parent);
61                            // interface method signatures are methods of it
62                            walk(ctx, child, Some(&name), out);
63                        }
64                        _ => {}
65                    }
66                }
67            }
68            // interface method signatures (node name varies by grammar version)
69            "method_spec" | "method_elem" => {
70                if let Some(name) = ctx.field_text(child, "name") {
71                    push(ctx, out, &name, Kind::Method, child, parent);
72                }
73            }
74            _ => walk(ctx, child, parent, out),
75        }
76    }
77}
78
79/// Emit a symbol carrying Go's capitalization-is-visibility convention:
80/// an exported (uppercase) name is public, an unexported one private.
81fn push(ctx: &Ctx, out: &mut Vec<Symbol>, name: &str, kind: Kind, node: Node, p: Option<&str>) {
82    let mut s = ctx.symbol(name, kind, node, p);
83    s.visibility = Some(if name.chars().next().is_some_and(char::is_uppercase) {
84        "public"
85    } else {
86        "private"
87    });
88    out.push(s);
89}
90
91/// The first `type_identifier` within `node` — used to pull the bare type
92/// name out of a receiver like `(s *Server)` or `(s *Stack[T])`.
93fn type_identifier(ctx: &Ctx, node: Node) -> Option<String> {
94    if node.kind() == "type_identifier" {
95        return ctx.node_text(node);
96    }
97    let mut cursor = node.walk();
98    for child in node.children(&mut cursor) {
99        if let Some(name) = type_identifier(ctx, child) {
100            return Some(name);
101        }
102    }
103    None
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    fn extract(source: &str) -> Vec<Symbol> {
111        Go.extract("test.go", source)
112    }
113
114    fn find<'a>(syms: &'a [Symbol], name: &str) -> &'a Symbol {
115        syms.iter()
116            .find(|s| s.name == name)
117            .unwrap_or_else(|| panic!("no symbol named {name} in {syms:?}"))
118    }
119
120    #[test]
121    fn extracts_funcs_types_and_methods() {
122        let src = r#"
123package widget
124
125type Widget struct {
126	Size int
127}
128
129type Renderer interface {
130	Render() string
131}
132
133func (w *Widget) Resize(n int) {
134	w.Size = n
135}
136
137func Build() *Widget {
138	return &Widget{}
139}
140"#;
141        let syms = extract(src);
142
143        assert_eq!(find(&syms, "Widget").kind, Kind::Struct);
144        assert_eq!(find(&syms, "Renderer").kind, Kind::Trait);
145
146        // a free func vs a method qualified by its receiver type
147        let build = find(&syms, "Build");
148        assert_eq!(build.kind, Kind::Function);
149        assert_eq!(build.parent, None);
150
151        let resize = find(&syms, "Resize");
152        assert_eq!(resize.kind, Kind::Method);
153        assert_eq!(resize.parent.as_deref(), Some("Widget"));
154
155        // an interface method signature is a method of the interface
156        let render = find(&syms, "Render");
157        assert_eq!(render.kind, Kind::Method);
158        assert_eq!(render.parent.as_deref(), Some("Renderer"));
159
160        assert_eq!(build.language, "go");
161    }
162
163    #[test]
164    fn empty_and_unparseable_yield_no_symbols() {
165        assert!(extract("").is_empty());
166        assert!(extract("package x\n").is_empty());
167    }
168
169    #[test]
170    fn capitalization_is_visibility() {
171        let src = "package x\n\nfunc Exported() {}\nfunc internal() {}\n";
172        let syms = extract(src);
173        assert_eq!(find(&syms, "Exported").visibility, Some("public"));
174        assert_eq!(find(&syms, "internal").visibility, Some("private"));
175    }
176}