1use tree_sitter::{Node, Parser};
5
6use super::types::*;
7
8pub fn parse(source: &str, file: &str, result: &mut ScanResult) {
9 let mut parser = Parser::new();
10 if parser
11 .set_language(&tree_sitter_go::LANGUAGE.into())
12 .is_err()
13 {
14 return;
15 }
16 let tree = match parser.parse(source, None) {
17 Some(t) => t,
18 None => return,
19 };
20 let is_test = file.ends_with("_test.go");
21 extract_go(&tree.root_node(), source, file, is_test, result);
22}
23
24fn extract_go(root: &Node, source: &str, file: &str, is_test: bool, result: &mut ScanResult) {
25 let mut cursor = root.walk();
26 for child in root.named_children(&mut cursor) {
27 match child.kind() {
28 "type_declaration" => extract_type_decl(&child, source, file, result),
29 "function_declaration" => extract_function(&child, source, file, is_test, result),
30 "method_declaration" => extract_method(&child, source, file, is_test, result),
31 _ => {}
32 }
33 }
34}
35
36fn extract_type_decl(node: &Node, source: &str, file: &str, result: &mut ScanResult) {
37 let mut cursor = node.walk();
38 for child in node.named_children(&mut cursor) {
39 if child.kind() == "type_spec" {
40 let name = match child.child_by_field_name("name") {
41 Some(n) => node_text(&n, source).to_string(),
42 None => continue,
43 };
44 let vis = go_visibility(&name);
45 let type_node = match child.child_by_field_name("type") {
46 Some(n) => n,
47 None => continue,
48 };
49
50 match type_node.kind() {
51 "struct_type" => {
52 let fields = extract_struct_fields(&type_node, source);
53 result.types.insert(
54 name.clone(),
55 TypeInfo {
56 name,
57 source: source_loc(file, &child),
58 kind: TypeKind::Struct,
59 fields,
60 visibility: vis,
61 ..Default::default()
62 },
63 );
64 }
65 "interface_type" => {
66 let methods = extract_interface_methods(&type_node, source);
67 result.types.insert(
68 name.clone(),
69 TypeInfo {
70 name,
71 source: source_loc(file, &child),
72 kind: TypeKind::Interface,
73 methods,
74 visibility: vis,
75 ..Default::default()
76 },
77 );
78 }
79 _ => {
80 result.types.insert(
81 name.clone(),
82 TypeInfo {
83 name,
84 source: source_loc(file, &child),
85 kind: TypeKind::TypeAlias,
86 visibility: vis,
87 ..Default::default()
88 },
89 );
90 }
91 }
92 }
93 }
94}
95
96fn extract_struct_fields(node: &Node, source: &str) -> Vec<Field> {
97 let mut fields = Vec::new();
98 let mut cursor = node.walk();
99 for child in node.named_children(&mut cursor) {
100 if child.kind() == "field_declaration_list" {
101 let mut list_cursor = child.walk();
102 for field_node in child.named_children(&mut list_cursor) {
103 if field_node.kind() == "field_declaration" {
104 extract_single_field(&field_node, source, &mut fields);
105 }
106 }
107 } else if child.kind() == "field_declaration" {
108 extract_single_field(&child, source, &mut fields);
109 }
110 }
111 fields
112}
113
114fn extract_single_field(field_node: &Node, source: &str, fields: &mut Vec<Field>) {
115 let type_name = field_node
116 .child_by_field_name("type")
117 .map(|t| node_text(&t, source).to_string())
118 .unwrap_or_default();
119 let mut inner = field_node.walk();
120 for name_child in field_node.named_children(&mut inner) {
121 if name_child.kind() == "field_identifier" {
122 let name = node_text(&name_child, source).to_string();
123 let optional = type_name.starts_with('*');
124 fields.push(Field {
125 name,
126 type_name: type_name.clone(),
127 optional,
128 });
129 }
130 }
131}
132
133fn extract_interface_methods(node: &Node, source: &str) -> Vec<String> {
134 let mut methods = Vec::new();
135 let mut cursor = node.walk();
136 for child in node.named_children(&mut cursor) {
137 if child.kind() == "method_spec" || child.kind() == "method_elem" {
138 if let Some(name_node) = child.child_by_field_name("name") {
139 methods.push(node_text(&name_node, source).to_string());
140 }
141 }
142 }
143 methods
144}
145
146fn extract_function(
147 node: &Node,
148 source: &str,
149 file: &str,
150 is_test_file: bool,
151 result: &mut ScanResult,
152) {
153 let name = match node.child_by_field_name("name") {
154 Some(n) => node_text(&n, source).to_string(),
155 None => return,
156 };
157 let vis = go_visibility(&name);
158 let is_test = is_test_file && name.starts_with("Test");
159 let sig = build_fn_signature(node, source, &name);
160
161 result.functions.insert(
162 name.clone(),
163 FunctionInfo {
164 name,
165 source: source_loc(file, node),
166 signature: sig,
167 visibility: vis,
168 is_async: false,
169 is_test,
170 },
171 );
172}
173
174fn extract_method(
175 node: &Node,
176 source: &str,
177 file: &str,
178 is_test_file: bool,
179 result: &mut ScanResult,
180) {
181 let name = match node.child_by_field_name("name") {
182 Some(n) => node_text(&n, source).to_string(),
183 None => return,
184 };
185 let receiver_type = extract_receiver_type(node, source);
186 let vis = go_visibility(&name);
187 let is_test = is_test_file && name.starts_with("Test");
188 let sig = build_fn_signature(node, source, &name);
189
190 let qualified = if receiver_type.is_empty() {
191 name.clone()
192 } else {
193 format!("{receiver_type}::{name}")
194 };
195 result.functions.insert(
196 qualified,
197 FunctionInfo {
198 name: name.clone(),
199 source: source_loc(file, node),
200 signature: sig,
201 visibility: vis,
202 is_async: false,
203 is_test,
204 },
205 );
206
207 if !receiver_type.is_empty() {
208 if let Some(typedef) = result.types.get_mut(&receiver_type) {
209 if !typedef.methods.contains(&name) {
210 typedef.methods.push(name);
211 }
212 }
213 }
214}
215
216fn extract_receiver_type(node: &Node, source: &str) -> String {
217 let receiver = match node.child_by_field_name("receiver") {
218 Some(r) => r,
219 None => return String::new(),
220 };
221 let mut cursor = receiver.walk();
222 for child in receiver.named_children(&mut cursor) {
223 if child.kind() == "parameter_declaration" {
224 if let Some(type_node) = child.child_by_field_name("type") {
225 let text = node_text(&type_node, source);
226 return text.trim_start_matches('*').to_string();
227 }
228 }
229 }
230 String::new()
231}
232
233fn build_fn_signature(node: &Node, source: &str, name: &str) -> String {
234 let params = node
235 .child_by_field_name("parameters")
236 .map(|n| node_text(&n, source))
237 .unwrap_or("()");
238 let ret = node
239 .child_by_field_name("result")
240 .map(|n| format!(" {}", node_text(&n, source)))
241 .unwrap_or_default();
242 format!("func {name}{params}{ret}")
243}
244
245fn go_visibility(name: &str) -> Visibility {
246 if name.starts_with(|c: char| c.is_uppercase()) {
247 Visibility::Public
248 } else {
249 Visibility::Private
250 }
251}
252
253fn node_text<'a>(node: &Node, source: &'a str) -> &'a str {
254 &source[node.byte_range()]
255}
256
257fn source_loc(file: &str, node: &Node) -> String {
258 format!("{}:{}", file, node.start_position().row + 1)
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 fn parse_go_str(source: &str) -> ScanResult {
266 let mut result = ScanResult::default();
267 parse(source, "main.go", &mut result);
268 result
269 }
270
271 #[test]
272 fn struct_with_fields() {
273 let r = parse_go_str(
274 r#"
275package main
276type User struct {
277 Name string
278 Email string
279 Age int
280}
281"#,
282 );
283 let t = &r.types["User"];
284 assert_eq!(t.kind, TypeKind::Struct);
285 assert_eq!(t.visibility, Visibility::Public);
286 assert_eq!(t.fields.len(), 3);
287 }
288
289 #[test]
290 fn unexported_is_private() {
291 let r = parse_go_str("package main\ntype config struct { debug bool }");
292 assert_eq!(r.types["config"].visibility, Visibility::Private);
293 }
294
295 #[test]
296 fn interface_methods() {
297 let r = parse_go_str(
298 r#"
299package main
300type Repository interface {
301 Get(id string) error
302 Save(item Item) error
303}
304"#,
305 );
306 let t = &r.types["Repository"];
307 assert_eq!(t.kind, TypeKind::Interface);
308 assert!(t.methods.contains(&"Get".to_string()));
309 assert!(t.methods.contains(&"Save".to_string()));
310 }
311
312 #[test]
313 fn method_adds_to_type() {
314 let r = parse_go_str(
315 r#"
316package main
317type Server struct { Port int }
318func (s *Server) Start() error { return nil }
319"#,
320 );
321 let t = &r.types["Server"];
322 assert!(t.methods.contains(&"Start".to_string()));
323 assert!(r.functions.contains_key("Server::Start"));
324 }
325
326 #[test]
327 fn pointer_field_optional() {
328 let r = parse_go_str(
329 "package main\ntype Config struct {\n Name string\n Parent *Config\n}",
330 );
331 let t = &r.types["Config"];
332 assert!(!t.fields[0].optional);
333 assert!(t.fields[1].optional);
334 }
335}