1use std::collections::hash_map::DefaultHasher;
2use std::hash::{Hash, Hasher};
3
4use cha_core::{ClassInfo, FunctionInfo, ImportInfo, SourceFile, SourceModel};
5use tree_sitter::{Node, Parser};
6
7use crate::LanguageParser;
8
9pub struct GolangParser;
10
11impl LanguageParser for GolangParser {
12 fn language_name(&self) -> &str {
13 "go"
14 }
15
16 fn parse(&self, file: &SourceFile) -> Option<SourceModel> {
17 let mut parser = Parser::new();
18 parser.set_language(&tree_sitter_go::LANGUAGE.into()).ok()?;
19 let tree = parser.parse(&file.content, None)?;
20 let root = tree.root_node();
21 let src = file.content.as_bytes();
22
23 let mut functions = Vec::new();
24 let mut classes = Vec::new();
25 let mut imports = Vec::new();
26
27 collect_top_level(root, src, &mut functions, &mut classes, &mut imports);
28
29 Some(SourceModel {
30 language: "go".into(),
31 total_lines: file.line_count(),
32 functions,
33 classes,
34 imports,
35 comments: collect_comments(root, src),
36 type_aliases: vec![], })
38 }
39}
40
41fn collect_top_level(
42 root: Node,
43 src: &[u8],
44 functions: &mut Vec<FunctionInfo>,
45 classes: &mut Vec<ClassInfo>,
46 imports: &mut Vec<ImportInfo>,
47) {
48 let mut cursor = root.walk();
49 for child in root.children(&mut cursor) {
50 match child.kind() {
51 "function_declaration" | "method_declaration" => {
52 if let Some(f) = extract_function(child, src) {
53 functions.push(f);
54 }
55 }
56 "type_declaration" => extract_type_decl(child, src, classes),
57 "import_declaration" => collect_imports(child, src, imports),
58 _ => {}
59 }
60 }
61}
62
63fn extract_function(node: Node, src: &[u8]) -> Option<FunctionInfo> {
64 let name = node_text(node.child_by_field_name("name")?, src).to_string();
65 let start_line = node.start_position().row + 1;
66 let end_line = node.end_position().row + 1;
67 let body = node.child_by_field_name("body");
68 let params = node.child_by_field_name("parameters");
69 let (param_count, param_types) = params
70 .map(|p| extract_params(p, src))
71 .unwrap_or((0, vec![]));
72 let is_exported = name.starts_with(|c: char| c.is_uppercase());
73
74 Some(FunctionInfo {
75 name,
76 start_line,
77 end_line,
78 line_count: end_line - start_line + 1,
79 complexity: count_complexity(node),
80 body_hash: body.map(hash_ast),
81 is_exported,
82 parameter_count: param_count,
83 parameter_types: param_types,
84 chain_depth: body.map(max_chain_depth).unwrap_or(0),
85 switch_arms: body.map(count_case_clauses).unwrap_or(0),
86 external_refs: body
87 .map(|b| collect_external_refs(b, src))
88 .unwrap_or_default(),
89 is_delegating: body.map(|b| check_delegating(b, src)).unwrap_or(false),
90 comment_lines: count_comment_lines(node, src),
91 referenced_fields: body
92 .map(|b| collect_field_refs_go(b, src))
93 .unwrap_or_default(),
94 null_check_fields: body.map(|b| collect_nil_checks(b, src)).unwrap_or_default(),
95 switch_dispatch_target: body.and_then(|b| extract_switch_target_go(b, src)),
96 optional_param_count: 0,
97 called_functions: collect_calls(body, src),
98 cognitive_complexity: body.map(|b| cognitive_complexity_go(b)).unwrap_or(0),
99 })
100}
101
102fn extract_type_decl(node: Node, src: &[u8], classes: &mut Vec<ClassInfo>) {
103 let mut cursor = node.walk();
104 for child in node.children(&mut cursor) {
105 if child.kind() == "type_spec"
106 && let Some(c) = extract_struct(child, src)
107 {
108 classes.push(c);
109 }
110 }
111}
112
113fn extract_struct(node: Node, src: &[u8]) -> Option<ClassInfo> {
114 let name = node_text(node.child_by_field_name("name")?, src).to_string();
115 let type_node = node.child_by_field_name("type")?;
116 if type_node.kind() != "struct_type" && type_node.kind() != "interface_type" {
117 return None;
118 }
119 let is_interface = type_node.kind() == "interface_type";
120 let start_line = node.start_position().row + 1;
121 let end_line = node.end_position().row + 1;
122 let field_count = count_struct_fields(type_node);
123 let is_exported = name.starts_with(|c: char| c.is_uppercase());
124
125 Some(ClassInfo {
126 name,
127 start_line,
128 end_line,
129 line_count: end_line - start_line + 1,
130 method_count: 0,
131 is_exported,
132 delegating_method_count: 0,
133 field_count,
134 field_names: Vec::new(),
135 field_types: Vec::new(),
136 has_behavior: false,
137 is_interface,
138 parent_name: None,
139 override_count: 0,
140 self_call_count: 0,
141 has_listener_field: false,
142 has_notify_method: false,
143 })
144}
145
146fn count_struct_fields(node: Node) -> usize {
147 let mut count = 0;
148 let mut cursor = node.walk();
149 visit_all(node, &mut cursor, &mut |n| {
150 if n.kind() == "field_declaration" {
151 count += 1;
152 }
153 });
154 count
155}
156
157fn collect_imports(node: Node, src: &[u8], imports: &mut Vec<ImportInfo>) {
158 let mut cursor = node.walk();
159 visit_all(node, &mut cursor, &mut |n| {
160 if n.kind() == "import_spec" {
161 let line = n.start_position().row + 1;
162 let path_node = n.child_by_field_name("path").unwrap_or(n);
163 let text = node_text(path_node, src).trim_matches('"').to_string();
164 if !text.is_empty() {
165 imports.push(ImportInfo {
166 source: text,
167 line,
168 ..Default::default()
169 });
170 }
171 }
172 });
173}
174
175fn extract_params(params: Node, src: &[u8]) -> (usize, Vec<String>) {
176 let mut count = 0;
177 let mut types = Vec::new();
178 let mut cursor = params.walk();
179 for child in params.children(&mut cursor) {
180 if child.kind() == "parameter_declaration" {
181 let ty = child
182 .child_by_field_name("type")
183 .map(|t| node_text(t, src).to_string())
184 .unwrap_or_else(|| "any".into());
185 let mut inner = child.walk();
187 let names: usize = child
188 .children(&mut inner)
189 .filter(|c| c.kind() == "identifier")
190 .count()
191 .max(1);
192 for _ in 0..names {
193 count += 1;
194 types.push(ty.clone());
195 }
196 }
197 }
198 (count, types)
199}
200
201fn count_complexity(node: Node) -> usize {
202 let mut c = 1usize;
203 let mut cursor = node.walk();
204 visit_all(node, &mut cursor, &mut |n| match n.kind() {
205 "if_statement" | "for_statement" | "expression_case" | "default_case" | "type_case"
206 | "select_statement" | "go_statement" => c += 1,
207 "binary_expression" => {
208 if let Some(op) = n.child_by_field_name("operator") {
209 let kind = op.kind();
210 if kind == "&&" || kind == "||" {
211 c += 1;
212 }
213 }
214 }
215 _ => {}
216 });
217 c
218}
219
220fn max_chain_depth(node: Node) -> usize {
221 let mut max = 0;
222 let mut cursor = node.walk();
223 visit_all(node, &mut cursor, &mut |n| {
224 if n.kind() == "selector_expression" {
225 let d = chain_len(n);
226 if d > max {
227 max = d;
228 }
229 }
230 });
231 max
232}
233
234fn chain_len(node: Node) -> usize {
235 let mut depth = 0;
236 let mut current = node;
237 while current.kind() == "selector_expression" || current.kind() == "call_expression" {
238 if current.kind() == "selector_expression" {
239 depth += 1;
240 }
241 match current.child(0) {
242 Some(c) => current = c,
243 None => break,
244 }
245 }
246 depth
247}
248
249fn count_case_clauses(node: Node) -> usize {
250 let mut count = 0;
251 let mut cursor = node.walk();
252 visit_all(node, &mut cursor, &mut |n| {
253 if n.kind() == "expression_case" || n.kind() == "default_case" || n.kind() == "type_case" {
254 count += 1;
255 }
256 });
257 count
258}
259
260fn collect_external_refs(node: Node, src: &[u8]) -> Vec<String> {
261 let mut refs = Vec::new();
262 let mut cursor = node.walk();
263 visit_all(node, &mut cursor, &mut |n| {
264 if n.kind() == "selector_expression"
265 && let Some(obj) = n.child(0)
266 && obj.kind() == "identifier"
267 {
268 let text = node_text(obj, src).to_string();
269 if !refs.contains(&text) {
270 refs.push(text);
271 }
272 }
273 });
274 refs
275}
276
277fn check_delegating(body: Node, src: &[u8]) -> bool {
278 let mut cursor = body.walk();
279 let stmts: Vec<Node> = body
280 .children(&mut cursor)
281 .filter(|n| n.kind() != "{" && n.kind() != "}" && n.kind() != "comment")
282 .collect();
283 if stmts.len() != 1 {
284 return false;
285 }
286 let stmt = stmts[0];
287 let call = match stmt.kind() {
288 "return_statement" => stmt.child(1).filter(|c| c.kind() == "call_expression"),
289 "expression_statement" => stmt.child(0).filter(|c| c.kind() == "call_expression"),
290 _ => None,
291 };
292 call.and_then(|c| c.child(0))
293 .is_some_and(|f| node_text(f, src).contains('.'))
294}
295
296fn count_comment_lines(node: Node, src: &[u8]) -> usize {
297 let mut count = 0;
298 let mut cursor = node.walk();
299 visit_all(node, &mut cursor, &mut |n| {
300 if n.kind() == "comment" {
301 count += node_text(n, src).lines().count();
302 }
303 });
304 count
305}
306
307fn collect_nil_checks(body: Node, src: &[u8]) -> Vec<String> {
308 let mut fields = Vec::new();
309 let mut cursor = body.walk();
310 visit_all(body, &mut cursor, &mut |n| {
311 if n.kind() != "binary_expression" {
312 return;
313 }
314 let text = node_text(n, src);
315 if !text.contains("nil") {
316 return;
317 }
318 if let Some(left) = n.child(0) {
319 let name = node_text(left, src).to_string();
320 if !fields.contains(&name) {
321 fields.push(name);
322 }
323 }
324 });
325 fields
326}
327
328fn hash_ast(node: Node) -> u64 {
329 let mut hasher = DefaultHasher::new();
330 hash_node(node, &mut hasher);
331 hasher.finish()
332}
333
334fn hash_node(node: Node, hasher: &mut DefaultHasher) {
335 node.kind().hash(hasher);
336 let mut cursor = node.walk();
337 for child in node.children(&mut cursor) {
338 hash_node(child, hasher);
339 }
340}
341
342fn cognitive_complexity_go(node: Node) -> usize {
343 let mut score = 0;
344 cc_walk(node, 0, &mut score);
345 score
346}
347
348fn cc_walk(node: Node, nesting: usize, score: &mut usize) {
349 match node.kind() {
350 "if_statement" => {
351 *score += 1 + nesting;
352 cc_children(node, nesting + 1, score);
353 return;
354 }
355 "for_statement" => {
356 *score += 1 + nesting;
357 cc_children(node, nesting + 1, score);
358 return;
359 }
360 "expression_switch_statement" | "type_switch_statement" | "select_statement" => {
361 *score += 1 + nesting;
362 cc_children(node, nesting + 1, score);
363 return;
364 }
365 "else_clause" => {
366 *score += 1; }
368 "binary_expression" => {
369 if let Some(op) = node.child_by_field_name("operator")
370 && (op.kind() == "&&" || op.kind() == "||")
371 {
372 *score += 1;
373 }
374 }
375 _ => {}
376 }
377 cc_children(node, nesting, score);
378}
379
380fn cc_children(node: Node, nesting: usize, score: &mut usize) {
381 let mut cursor = node.walk();
382 for child in node.children(&mut cursor) {
383 cc_walk(child, nesting, score);
384 }
385}
386
387fn collect_field_refs_go(body: Node, src: &[u8]) -> Vec<String> {
388 let mut refs = Vec::new();
389 let mut cursor = body.walk();
390 visit_all(body, &mut cursor, &mut |n| {
391 if n.kind() == "selector_expression"
392 && let Some(field) = n.child_by_field_name("field")
393 {
394 let name = node_text(field, src).to_string();
395 if !refs.contains(&name) {
396 refs.push(name);
397 }
398 }
399 });
400 refs
401}
402
403fn extract_switch_target_go(body: Node, src: &[u8]) -> Option<String> {
404 let mut target = None;
405 let mut cursor = body.walk();
406 visit_all(body, &mut cursor, &mut |n| {
407 if (n.kind() == "expression_switch_statement" || n.kind() == "type_switch_statement")
408 && target.is_none()
409 && let Some(val) = n.child_by_field_name("value")
410 {
411 target = Some(node_text(val, src).to_string());
412 }
413 });
414 target
415}
416
417fn collect_calls(body: Option<Node>, src: &[u8]) -> Vec<String> {
418 let Some(body) = body else { return Vec::new() };
419 let mut calls = Vec::new();
420 let mut cursor = body.walk();
421 visit_all(body, &mut cursor, &mut |n| {
422 if n.kind() == "call_expression"
423 && let Some(func) = n.child(0)
424 {
425 let name = node_text(func, src).to_string();
426 if !calls.contains(&name) {
427 calls.push(name);
428 }
429 }
430 });
431 calls
432}
433
434fn node_text<'a>(node: Node, src: &'a [u8]) -> &'a str {
435 node.utf8_text(src).unwrap_or("")
436}
437
438fn collect_comments(root: Node, src: &[u8]) -> Vec<cha_core::CommentInfo> {
439 let mut comments = Vec::new();
440 let mut cursor = root.walk();
441 visit_all(root, &mut cursor, &mut |n| {
442 if n.kind().contains("comment") {
443 comments.push(cha_core::CommentInfo {
444 text: node_text(n, src).to_string(),
445 line: n.start_position().row + 1,
446 });
447 }
448 });
449 comments
450}
451
452fn visit_all<F: FnMut(Node)>(node: Node, cursor: &mut tree_sitter::TreeCursor, f: &mut F) {
453 f(node);
454 if cursor.goto_first_child() {
455 loop {
456 let child_node = cursor.node();
457 let mut child_cursor = child_node.walk();
458 visit_all(child_node, &mut child_cursor, f);
459 if !cursor.goto_next_sibling() {
460 break;
461 }
462 }
463 cursor.goto_parent();
464 }
465}