reference_query/lang/go/
mod.rs1use 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 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 self.walk(child, Some(&name), out);
79 }
80 _ => {}
81 }
82 }
83 }
84 "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 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 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 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}