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 { source: text, line });
166 }
167 }
168 });
169}
170
171fn extract_params(params: Node, src: &[u8]) -> (usize, Vec<String>) {
172 let mut count = 0;
173 let mut types = Vec::new();
174 let mut cursor = params.walk();
175 for child in params.children(&mut cursor) {
176 if child.kind() == "parameter_declaration" {
177 let ty = child
178 .child_by_field_name("type")
179 .map(|t| node_text(t, src).to_string())
180 .unwrap_or_else(|| "any".into());
181 let mut inner = child.walk();
183 let names: usize = child
184 .children(&mut inner)
185 .filter(|c| c.kind() == "identifier")
186 .count()
187 .max(1);
188 for _ in 0..names {
189 count += 1;
190 types.push(ty.clone());
191 }
192 }
193 }
194 (count, types)
195}
196
197fn count_complexity(node: Node) -> usize {
198 let mut c = 1usize;
199 let mut cursor = node.walk();
200 visit_all(node, &mut cursor, &mut |n| match n.kind() {
201 "if_statement" | "for_statement" | "expression_case" | "default_case" | "type_case"
202 | "select_statement" | "go_statement" => c += 1,
203 "binary_expression" => {
204 if let Some(op) = n.child_by_field_name("operator") {
205 let kind = op.kind();
206 if kind == "&&" || kind == "||" {
207 c += 1;
208 }
209 }
210 }
211 _ => {}
212 });
213 c
214}
215
216fn max_chain_depth(node: Node) -> usize {
217 let mut max = 0;
218 let mut cursor = node.walk();
219 visit_all(node, &mut cursor, &mut |n| {
220 if n.kind() == "selector_expression" {
221 let d = chain_len(n);
222 if d > max {
223 max = d;
224 }
225 }
226 });
227 max
228}
229
230fn chain_len(node: Node) -> usize {
231 let mut depth = 0;
232 let mut current = node;
233 while current.kind() == "selector_expression" || current.kind() == "call_expression" {
234 if current.kind() == "selector_expression" {
235 depth += 1;
236 }
237 match current.child(0) {
238 Some(c) => current = c,
239 None => break,
240 }
241 }
242 depth
243}
244
245fn count_case_clauses(node: Node) -> usize {
246 let mut count = 0;
247 let mut cursor = node.walk();
248 visit_all(node, &mut cursor, &mut |n| {
249 if n.kind() == "expression_case" || n.kind() == "default_case" || n.kind() == "type_case" {
250 count += 1;
251 }
252 });
253 count
254}
255
256fn collect_external_refs(node: Node, src: &[u8]) -> Vec<String> {
257 let mut refs = Vec::new();
258 let mut cursor = node.walk();
259 visit_all(node, &mut cursor, &mut |n| {
260 if n.kind() == "selector_expression"
261 && let Some(obj) = n.child(0)
262 && obj.kind() == "identifier"
263 {
264 let text = node_text(obj, src).to_string();
265 if !refs.contains(&text) {
266 refs.push(text);
267 }
268 }
269 });
270 refs
271}
272
273fn check_delegating(body: Node, src: &[u8]) -> bool {
274 let mut cursor = body.walk();
275 let stmts: Vec<Node> = body
276 .children(&mut cursor)
277 .filter(|n| n.kind() != "{" && n.kind() != "}" && n.kind() != "comment")
278 .collect();
279 if stmts.len() != 1 {
280 return false;
281 }
282 let stmt = stmts[0];
283 let call = match stmt.kind() {
284 "return_statement" => stmt.child(1).filter(|c| c.kind() == "call_expression"),
285 "expression_statement" => stmt.child(0).filter(|c| c.kind() == "call_expression"),
286 _ => None,
287 };
288 call.and_then(|c| c.child(0))
289 .is_some_and(|f| node_text(f, src).contains('.'))
290}
291
292fn count_comment_lines(node: Node, src: &[u8]) -> usize {
293 let mut count = 0;
294 let mut cursor = node.walk();
295 visit_all(node, &mut cursor, &mut |n| {
296 if n.kind() == "comment" {
297 count += node_text(n, src).lines().count();
298 }
299 });
300 count
301}
302
303fn collect_nil_checks(body: Node, src: &[u8]) -> Vec<String> {
304 let mut fields = Vec::new();
305 let mut cursor = body.walk();
306 visit_all(body, &mut cursor, &mut |n| {
307 if n.kind() != "binary_expression" {
308 return;
309 }
310 let text = node_text(n, src);
311 if !text.contains("nil") {
312 return;
313 }
314 if let Some(left) = n.child(0) {
315 let name = node_text(left, src).to_string();
316 if !fields.contains(&name) {
317 fields.push(name);
318 }
319 }
320 });
321 fields
322}
323
324fn hash_ast(node: Node) -> u64 {
325 let mut hasher = DefaultHasher::new();
326 hash_node(node, &mut hasher);
327 hasher.finish()
328}
329
330fn hash_node(node: Node, hasher: &mut DefaultHasher) {
331 node.kind().hash(hasher);
332 let mut cursor = node.walk();
333 for child in node.children(&mut cursor) {
334 hash_node(child, hasher);
335 }
336}
337
338fn cognitive_complexity_go(node: Node) -> usize {
339 let mut score = 0;
340 cc_walk(node, 0, &mut score);
341 score
342}
343
344fn cc_walk(node: Node, nesting: usize, score: &mut usize) {
345 match node.kind() {
346 "if_statement" => {
347 *score += 1 + nesting;
348 cc_children(node, nesting + 1, score);
349 return;
350 }
351 "for_statement" => {
352 *score += 1 + nesting;
353 cc_children(node, nesting + 1, score);
354 return;
355 }
356 "expression_switch_statement" | "type_switch_statement" | "select_statement" => {
357 *score += 1 + nesting;
358 cc_children(node, nesting + 1, score);
359 return;
360 }
361 "else_clause" => {
362 *score += 1; }
364 "binary_expression" => {
365 if let Some(op) = node.child_by_field_name("operator")
366 && (op.kind() == "&&" || op.kind() == "||")
367 {
368 *score += 1;
369 }
370 }
371 _ => {}
372 }
373 cc_children(node, nesting, score);
374}
375
376fn cc_children(node: Node, nesting: usize, score: &mut usize) {
377 let mut cursor = node.walk();
378 for child in node.children(&mut cursor) {
379 cc_walk(child, nesting, score);
380 }
381}
382
383fn collect_field_refs_go(body: Node, src: &[u8]) -> Vec<String> {
384 let mut refs = Vec::new();
385 let mut cursor = body.walk();
386 visit_all(body, &mut cursor, &mut |n| {
387 if n.kind() == "selector_expression"
388 && let Some(field) = n.child_by_field_name("field")
389 {
390 let name = node_text(field, src).to_string();
391 if !refs.contains(&name) {
392 refs.push(name);
393 }
394 }
395 });
396 refs
397}
398
399fn extract_switch_target_go(body: Node, src: &[u8]) -> Option<String> {
400 let mut target = None;
401 let mut cursor = body.walk();
402 visit_all(body, &mut cursor, &mut |n| {
403 if (n.kind() == "expression_switch_statement" || n.kind() == "type_switch_statement")
404 && target.is_none()
405 && let Some(val) = n.child_by_field_name("value")
406 {
407 target = Some(node_text(val, src).to_string());
408 }
409 });
410 target
411}
412
413fn collect_calls(body: Option<Node>, src: &[u8]) -> Vec<String> {
414 let Some(body) = body else { return Vec::new() };
415 let mut calls = Vec::new();
416 let mut cursor = body.walk();
417 visit_all(body, &mut cursor, &mut |n| {
418 if n.kind() == "call_expression"
419 && let Some(func) = n.child(0)
420 {
421 let name = node_text(func, src).to_string();
422 if !calls.contains(&name) {
423 calls.push(name);
424 }
425 }
426 });
427 calls
428}
429
430fn node_text<'a>(node: Node, src: &'a [u8]) -> &'a str {
431 node.utf8_text(src).unwrap_or("")
432}
433
434fn collect_comments(root: Node, src: &[u8]) -> Vec<cha_core::CommentInfo> {
435 let mut comments = Vec::new();
436 let mut cursor = root.walk();
437 visit_all(root, &mut cursor, &mut |n| {
438 if n.kind().contains("comment") {
439 comments.push(cha_core::CommentInfo {
440 text: node_text(n, src).to_string(),
441 line: n.start_position().row + 1,
442 });
443 }
444 });
445 comments
446}
447
448fn visit_all<F: FnMut(Node)>(node: Node, cursor: &mut tree_sitter::TreeCursor, f: &mut F) {
449 f(node);
450 if cursor.goto_first_child() {
451 loop {
452 let child_node = cursor.node();
453 let mut child_cursor = child_node.walk();
454 visit_all(child_node, &mut child_cursor, f);
455 if !cursor.goto_next_sibling() {
456 break;
457 }
458 }
459 cursor.goto_parent();
460 }
461}