reference_query/lang/go/
mod.rs1use 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 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 walk(ctx, child, Some(&name), out);
63 }
64 _ => {}
65 }
66 }
67 }
68 "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
79fn 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
91fn 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 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 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}