1use crate::docstring::extract_preceding_prefix_comments;
4use crate::{ContainerBody, Import, Language, LanguageSymbols, Visibility};
5use tree_sitter::Node;
6
7pub struct Go;
9
10impl Language for Go {
11 fn name(&self) -> &'static str {
12 "Go"
13 }
14 fn extensions(&self) -> &'static [&'static str] {
15 &["go"]
16 }
17 fn grammar_name(&self) -> &'static str {
18 "go"
19 }
20
21 fn as_symbols(&self) -> Option<&dyn LanguageSymbols> {
22 Some(self)
23 }
24
25 fn signature_suffix(&self) -> &'static str {
26 " {}"
27 }
28
29 fn extract_docstring(&self, node: &Node, content: &str) -> Option<String> {
30 extract_preceding_prefix_comments(node, content, "//")
31 }
32
33 fn refine_kind(
34 &self,
35 node: &Node,
36 _content: &str,
37 tag_kind: crate::SymbolKind,
38 ) -> crate::SymbolKind {
39 if node.kind() == "type_spec"
41 && let Some(type_node) = node.child_by_field_name("type")
42 {
43 return match type_node.kind() {
44 "struct_type" => crate::SymbolKind::Struct,
45 "interface_type" => crate::SymbolKind::Interface,
46 _ => tag_kind,
47 };
48 }
49 tag_kind
50 }
51
52 fn build_signature(&self, node: &Node, content: &str) -> String {
53 let name = match self.node_name(node, content) {
54 Some(n) => n,
55 None => {
56 return content[node.byte_range()]
57 .lines()
58 .next()
59 .unwrap_or("")
60 .trim()
61 .to_string();
62 }
63 };
64 match node.kind() {
65 "function_declaration" | "method_declaration" => {
66 let params = node
67 .child_by_field_name("parameters")
68 .map(|p| content[p.byte_range()].to_string())
69 .unwrap_or_else(|| "()".to_string());
70 format!("func {}{}", name, params)
71 }
72 "type_spec" => format!("type {}", name),
73 _ => {
74 let text = &content[node.byte_range()];
75 text.lines().next().unwrap_or(text).trim().to_string()
76 }
77 }
78 }
79
80 fn extract_imports(&self, node: &Node, content: &str) -> Vec<Import> {
81 if node.kind() != "import_declaration" {
82 return Vec::new();
83 }
84
85 let mut imports = Vec::new();
86 let line = node.start_position().row + 1;
87
88 let mut cursor = node.walk();
89 for child in node.children(&mut cursor) {
90 match child.kind() {
91 "import_spec" => {
92 if let Some(imp) = Self::parse_import_spec(&child, content, line) {
94 imports.push(imp);
95 }
96 }
97 "import_spec_list" => {
98 let mut list_cursor = child.walk();
100 for spec in child.children(&mut list_cursor) {
101 if spec.kind() == "import_spec"
102 && let Some(imp) = Self::parse_import_spec(&spec, content, line)
103 {
104 imports.push(imp);
105 }
106 }
107 }
108 _ => {}
109 }
110 }
111
112 imports
113 }
114
115 fn format_import(&self, import: &Import, _names: Option<&[&str]>) -> String {
116 if let Some(ref alias) = import.alias {
118 format!("import {} \"{}\"", alias, import.module)
119 } else {
120 format!("import \"{}\"", import.module)
121 }
122 }
123
124 fn get_visibility(&self, node: &Node, content: &str) -> Visibility {
125 let is_exported = self
126 .node_name(node, content)
127 .and_then(|n| n.chars().next())
128 .map(|c| c.is_uppercase())
129 .unwrap_or(false);
130 if is_exported {
131 Visibility::Public
132 } else {
133 Visibility::Private
134 }
135 }
136
137 fn is_test_symbol(&self, symbol: &crate::Symbol) -> bool {
138 match symbol.kind {
139 crate::SymbolKind::Function => {
140 let name = symbol.name.as_str();
141 name.starts_with("Test")
142 || name.starts_with("Benchmark")
143 || name.starts_with("Example")
144 }
145 _ => false,
146 }
147 }
148
149 fn test_file_globs(&self) -> &'static [&'static str] {
150 &["**/*_test.go"]
151 }
152
153 fn extract_module_doc(&self, src: &str) -> Option<String> {
154 extract_go_package_doc(src)
155 }
156
157 fn container_body<'a>(&self, node: &'a Node<'a>) -> Option<Node<'a>> {
158 node.child_by_field_name("body")
159 }
160
161 fn analyze_container_body(
162 &self,
163 body_node: &Node,
164 content: &str,
165 inner_indent: &str,
166 ) -> Option<ContainerBody> {
167 crate::body::analyze_brace_body(body_node, content, inner_indent)
168 }
169}
170
171impl LanguageSymbols for Go {}
172
173fn extract_go_package_doc(src: &str) -> Option<String> {
179 let lines: Vec<&str> = src.lines().collect();
180 let pkg_idx = lines.iter().position(|l| {
182 let t = l.trim();
183 t.starts_with("package ") || t == "package"
184 })?;
185
186 if pkg_idx > 0 && lines[pkg_idx - 1].trim().is_empty() {
188 return None;
189 }
190
191 let mut doc_lines: Vec<&str> = Vec::new();
193 let mut idx = pkg_idx;
194 while idx > 0 {
195 idx -= 1;
196 let t = lines[idx].trim();
197 if t.starts_with("//") {
198 doc_lines.push(t);
199 } else {
200 break;
201 }
202 }
203
204 if doc_lines.is_empty() {
205 return None;
206 }
207
208 doc_lines.reverse();
210 let text = doc_lines
211 .iter()
212 .map(|l| l.trim_start_matches("//").trim_start())
213 .collect::<Vec<_>>()
214 .join("\n")
215 .trim()
216 .to_string();
217
218 if text.is_empty() { None } else { Some(text) }
219}
220
221impl Go {
222 fn parse_import_spec(node: &Node, content: &str, line: usize) -> Option<Import> {
223 let mut path = String::new();
224 let mut alias = None;
225
226 let mut cursor = node.walk();
227 for child in node.children(&mut cursor) {
228 match child.kind() {
229 "interpreted_string_literal" => {
230 let text = &content[child.byte_range()];
231 path = text.trim_matches('"').to_string();
232 }
233 "package_identifier" | "blank_identifier" | "dot" => {
234 alias = Some(content[child.byte_range()].to_string());
235 }
236 _ => {}
237 }
238 }
239
240 if path.is_empty() {
241 return None;
242 }
243
244 let is_wildcard = alias.as_deref() == Some(".");
245 Some(Import {
246 module: path,
247 names: Vec::new(),
248 alias,
249 is_wildcard,
250 is_relative: false, line,
252 })
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
263 fn unused_node_kinds_audit() {
264 use crate::validate_unused_kinds_audit;
265
266 #[rustfmt::skip]
267 let documented_unused: &[&str] = &[
268 "blank_identifier", "field_declaration", "field_declaration_list", "field_identifier", "package_identifier", "parameter_declaration", "statement_list", "variadic_parameter_declaration", "default_case", "for_clause", "import_spec", "import_spec_list", "method_elem", "range_clause", "index_expression", "slice_expression", "type_assertion_expression", "type_conversion_expression", "type_instantiation_expression", "unary_expression", "array_type", "channel_type", "implicit_length_array_type", "function_type", "generic_type", "interface_type", "map_type", "negated_type", "parenthesized_type", "pointer_type", "qualified_type", "slice_type", "struct_type", "type_arguments", "type_constraint", "type_elem", "type_parameter_declaration", "type_parameter_list", "assignment_statement", "dec_statement", "expression_list", "expression_statement", "inc_statement", "short_var_declaration", "type_alias", "empty_statement", "fallthrough_statement", "go_statement", "labeled_statement", "receive_statement", "send_statement", "return_statement",
332 "continue_statement",
333 "break_statement",
334 "if_statement",
335 "for_statement",
336 "goto_statement",
337 "expression_switch_statement",
338 "expression_case",
339 "type_case",
340 "type_switch_statement",
341 "select_statement",
342 "block",
343 "defer_statement",
344 "binary_expression",
345 "communication_case",
346 ];
347
348 validate_unused_kinds_audit(&Go, documented_unused)
349 .expect("Go unused node kinds audit failed");
350 }
351}