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