use crate::models::{Class, Function};
use crate::parsers::{ImportInfo, ParseResult};
use anyhow::{Context, Result};
use std::collections::HashMap;
use std::path::Path;
use tree_sitter::{Node, Parser, Query, QueryCursor};
pub fn parse(path: &Path) -> Result<ParseResult> {
let source = std::fs::read_to_string(path)
.with_context(|| format!("Failed to read file: {}", path.display()))?;
parse_source(&source, path)
}
pub fn parse_source(source: &str, path: &Path) -> Result<ParseResult> {
let mut parser = Parser::new();
let language = tree_sitter_go::LANGUAGE;
parser
.set_language(&language.into())
.context("Failed to set Go language")?;
let tree = parser
.parse(source, None)
.context("Failed to parse Go source")?;
let root = tree.root_node();
let source_bytes = source.as_bytes();
let mut result = ParseResult::default();
extract_functions(&root, source_bytes, path, &mut result)?;
extract_structs_and_interfaces(&root, source_bytes, path, &mut result)?;
extract_imports(&root, source_bytes, &mut result)?;
extract_calls(&root, source_bytes, path, &mut result)?;
Ok(result)
}
fn extract_functions(
root: &Node,
source: &[u8],
path: &Path,
result: &mut ParseResult,
) -> Result<()> {
let func_query_str = r#"
(function_declaration
name: (identifier) @func_name
parameters: (parameter_list) @params
result: (_)? @return_type
) @func
"#;
let language = tree_sitter_go::LANGUAGE;
let query = Query::new(&language.into(), func_query_str).context("Failed to create function query")?;
let mut cursor = QueryCursor::new();
let mut matches = cursor.matches(&query, *root, source);
while let Some(m) = matches.next() {
let mut func_node = None;
let mut name = String::new();
let mut params_node = None;
let mut return_type_node = None;
for capture in m.captures.iter() {
let capture_name = query.capture_names()[capture.index as usize];
match capture_name {
"func" => func_node = Some(capture.node),
"func_name" => {
name = capture.node.utf8_text(source).unwrap_or("").to_string();
}
"params" => params_node = Some(capture.node),
"return_type" => return_type_node = Some(capture.node),
_ => {}
}
}
if let Some(node) = func_node {
let parameters = extract_parameters(params_node, source);
let return_type = return_type_node
.map(|n| n.utf8_text(source).unwrap_or("").to_string());
let line_start = node.start_position().row as u32 + 1;
let line_end = node.end_position().row as u32 + 1;
let qualified_name = format!("{}::{}:{}", path.display(), name, line_start);
result.functions.push(Function {
name: name.clone(),
qualified_name,
file_path: path.to_path_buf(),
line_start,
line_end,
parameters,
return_type,
is_async: false, complexity: Some(calculate_complexity(&node, source)),
});
}
}
extract_methods(root, source, path, result)?;
Ok(())
}
fn extract_methods(
root: &Node,
source: &[u8],
path: &Path,
result: &mut ParseResult,
) -> Result<()> {
let method_query_str = r#"
(method_declaration
receiver: (parameter_list) @receiver
name: (field_identifier) @method_name
parameters: (parameter_list) @params
result: (_)? @return_type
) @method
"#;
let language = tree_sitter_go::LANGUAGE;
let query = Query::new(&language.into(), method_query_str).context("Failed to create method query")?;
let mut cursor = QueryCursor::new();
let mut matches = cursor.matches(&query, *root, source);
while let Some(m) = matches.next() {
let mut method_node = None;
let mut name = String::new();
let mut receiver_node = None;
let mut params_node = None;
let mut return_type_node = None;
for capture in m.captures.iter() {
let capture_name = query.capture_names()[capture.index as usize];
match capture_name {
"method" => method_node = Some(capture.node),
"method_name" => {
name = capture.node.utf8_text(source).unwrap_or("").to_string();
}
"receiver" => receiver_node = Some(capture.node),
"params" => params_node = Some(capture.node),
"return_type" => return_type_node = Some(capture.node),
_ => {}
}
}
if let Some(node) = method_node {
let receiver_type = extract_receiver_type(receiver_node, source);
let parameters = extract_parameters(params_node, source);
let return_type = return_type_node
.map(|n| n.utf8_text(source).unwrap_or("").to_string());
let line_start = node.start_position().row as u32 + 1;
let line_end = node.end_position().row as u32 + 1;
let qualified_name = if let Some(ref recv) = receiver_type {
format!("{}::({}).{}:{}", path.display(), recv, name, line_start)
} else {
format!("{}::{}:{}", path.display(), name, line_start)
};
result.functions.push(Function {
name: name.clone(),
qualified_name,
file_path: path.to_path_buf(),
line_start,
line_end,
parameters,
return_type,
is_async: false,
complexity: Some(calculate_complexity(&node, source)),
});
}
}
Ok(())
}
fn extract_receiver_type(receiver_node: Option<Node>, source: &[u8]) -> Option<String> {
let node = receiver_node?;
for child in node.children(&mut node.walk()) {
if child.kind() == "parameter_declaration" {
if let Some(type_node) = child.child_by_field_name("type") {
return type_node.utf8_text(source).ok().map(|s| s.to_string());
}
let mut last_type = None;
for grandchild in child.children(&mut child.walk()) {
if grandchild.kind() == "type_identifier" || grandchild.kind() == "pointer_type" {
last_type = Some(grandchild);
}
}
if let Some(type_node) = last_type {
return type_node.utf8_text(source).ok().map(|s| s.to_string());
}
}
}
None
}
fn extract_parameters(params_node: Option<Node>, source: &[u8]) -> Vec<String> {
let Some(node) = params_node else {
return vec![];
};
let mut params = Vec::new();
for child in node.children(&mut node.walk()) {
if child.kind() == "parameter_declaration" {
for grandchild in child.children(&mut child.walk()) {
if grandchild.kind() == "identifier" {
if let Ok(text) = grandchild.utf8_text(source) {
params.push(text.to_string());
}
}
}
}
}
params
}
fn extract_structs_and_interfaces(
root: &Node,
source: &[u8],
path: &Path,
result: &mut ParseResult,
) -> Result<()> {
let query_str = r#"
(type_declaration
(type_spec
name: (type_identifier) @type_name
type: (struct_type) @struct_body
)
) @struct_decl
(type_declaration
(type_spec
name: (type_identifier) @iface_name
type: (interface_type) @iface_body
)
) @iface_decl
"#;
let language = tree_sitter_go::LANGUAGE;
let query = Query::new(&language.into(), query_str).context("Failed to create type query")?;
let mut cursor = QueryCursor::new();
let mut matches = cursor.matches(&query, *root, source);
while let Some(m) = matches.next() {
let mut decl_node = None;
let mut name = String::new();
let mut _is_struct = false;
let mut is_interface = false;
let mut body_node = None;
for capture in m.captures.iter() {
let capture_name = query.capture_names()[capture.index as usize];
match capture_name {
"struct_decl" => {
decl_node = Some(capture.node);
_is_struct = true;
}
"iface_decl" => {
decl_node = Some(capture.node);
is_interface = true;
}
"type_name" | "iface_name" => {
name = capture.node.utf8_text(source).unwrap_or("").to_string();
}
"struct_body" | "iface_body" => {
body_node = Some(capture.node);
}
_ => {}
}
}
if let Some(node) = decl_node {
let line_start = node.start_position().row as u32 + 1;
let line_end = node.end_position().row as u32 + 1;
let qualified_name = if is_interface {
format!("{}::interface::{}:{}", path.display(), name, line_start)
} else {
format!("{}::{}:{}", path.display(), name, line_start)
};
let methods = if is_interface {
extract_interface_methods(body_node, source)
} else {
vec![]
};
result.classes.push(Class {
name: name.clone(),
qualified_name,
file_path: path.to_path_buf(),
line_start,
line_end,
methods,
bases: vec![],
});
}
}
Ok(())
}
fn extract_interface_methods(body_node: Option<Node>, source: &[u8]) -> Vec<String> {
let Some(node) = body_node else {
return vec![];
};
let mut methods = Vec::new();
for child in node.children(&mut node.walk()) {
if child.kind() == "method_elem" {
if let Some(name_node) = child.child_by_field_name("name") {
if let Ok(name) = name_node.utf8_text(source) {
methods.push(name.to_string());
}
}
}
}
methods
}
fn extract_imports(root: &Node, source: &[u8], result: &mut ParseResult) -> Result<()> {
let query_str = r#"
(import_declaration
(import_spec
path: (interpreted_string_literal) @import_path
)
)
(import_declaration
(import_spec_list
(import_spec
path: (interpreted_string_literal) @import_path
)
)
)
"#;
let language = tree_sitter_go::LANGUAGE;
let query = Query::new(&language.into(), query_str).context("Failed to create import query")?;
let mut cursor = QueryCursor::new();
let mut matches = cursor.matches(&query, *root, source);
while let Some(m) = matches.next() {
for capture in m.captures.iter() {
if let Ok(text) = capture.node.utf8_text(source) {
let import = text.trim_matches('"').to_string();
if !import.is_empty() {
result.imports.push(ImportInfo::runtime(import));
}
}
}
}
Ok(())
}
fn extract_calls(
root: &Node,
source: &[u8],
path: &Path,
result: &mut ParseResult,
) -> Result<()> {
let mut scope_map: HashMap<(u32, u32), String> = HashMap::new();
for func in &result.functions {
scope_map.insert(
(func.line_start, func.line_end),
func.qualified_name.clone(),
);
}
extract_calls_recursive(root, source, path, &scope_map, result);
Ok(())
}
fn extract_calls_recursive(
node: &Node,
source: &[u8],
path: &Path,
scope_map: &HashMap<(u32, u32), String>,
result: &mut ParseResult,
) {
if node.kind() == "call_expression" {
let call_line = node.start_position().row as u32 + 1;
let caller = find_containing_scope(call_line, scope_map)
.unwrap_or_else(|| path.display().to_string());
if let Some(func_node) = node.child_by_field_name("function") {
if let Some(callee) = extract_call_target(&func_node, source) {
result.calls.push((caller, callee));
}
}
}
for child in node.children(&mut node.walk()) {
extract_calls_recursive(&child, source, path, scope_map, result);
}
}
fn find_containing_scope(line: u32, scope_map: &HashMap<(u32, u32), String>) -> Option<String> {
let mut best_match: Option<(&(u32, u32), &String)> = None;
for (range, name) in scope_map {
if line >= range.0 && line <= range.1 {
match best_match {
None => best_match = Some((range, name)),
Some((best_range, _)) => {
if (range.1 - range.0) < (best_range.1 - best_range.0) {
best_match = Some((range, name));
}
}
}
}
}
best_match.map(|(_, name)| name.clone())
}
fn extract_call_target(node: &Node, source: &[u8]) -> Option<String> {
match node.kind() {
"identifier" => node.utf8_text(source).ok().map(|s| s.to_string()),
"selector_expression" => {
node.utf8_text(source).ok().map(|s| s.to_string())
}
"parenthesized_expression" => {
for child in node.children(&mut node.walk()) {
if let Some(target) = extract_call_target(&child, source) {
return Some(target);
}
}
None
}
_ => node.utf8_text(source).ok().map(|s| s.to_string()),
}
}
fn calculate_complexity(node: &Node, _source: &[u8]) -> u32 {
let mut complexity = 1;
fn count_branches(node: &Node, complexity: &mut u32) {
match node.kind() {
"if_statement" | "for_statement" | "range_clause" => {
*complexity += 1;
}
"expression_case" | "default_case" | "type_case" => {
*complexity += 1;
}
"binary_expression" => {
for child in node.children(&mut node.walk()) {
if child.kind() == "&&" || child.kind() == "||" {
*complexity += 1;
}
}
}
"select_statement" | "communication_case" => {
*complexity += 1;
}
_ => {}
}
for child in node.children(&mut node.walk()) {
count_branches(&child, complexity);
}
}
count_branches(node, &mut complexity);
complexity
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_parse_simple_function() {
let source = r#"
package main
func hello(name string) string {
return "Hello, " + name
}
"#;
let path = PathBuf::from("test.go");
let result = parse_source(source, &path).unwrap();
assert_eq!(result.functions.len(), 1);
let func = &result.functions[0];
assert_eq!(func.name, "hello");
}
#[test]
fn test_parse_method() {
let source = r#"
package main
type MyStruct struct {
value int
}
func (s *MyStruct) GetValue() int {
return s.value
}
"#;
let path = PathBuf::from("test.go");
let result = parse_source(source, &path).unwrap();
assert!(result.functions.iter().any(|f| f.name == "GetValue"));
}
#[test]
fn test_parse_struct() {
let source = r#"
package main
type Person struct {
Name string
Age int
}
"#;
let path = PathBuf::from("test.go");
let result = parse_source(source, &path).unwrap();
assert_eq!(result.classes.len(), 1);
let class = &result.classes[0];
assert_eq!(class.name, "Person");
}
#[test]
fn test_parse_interface() {
let source = r#"
package main
type Reader interface {
Read(p []byte) (n int, err error)
}
"#;
let path = PathBuf::from("test.go");
let result = parse_source(source, &path).unwrap();
assert_eq!(result.classes.len(), 1);
let iface = &result.classes[0];
assert_eq!(iface.name, "Reader");
}
#[test]
fn test_parse_imports() {
let source = r#"
package main
import (
"fmt"
"os"
)
"#;
let path = PathBuf::from("test.go");
let result = parse_source(source, &path).unwrap();
assert!(result.imports.iter().any(|i| i.path == "fmt"));
assert!(result.imports.iter().any(|i| i.path == "os"));
}
#[test]
fn test_method_count_excludes_nested_closures() {
let source = r#"
package main
type Handler struct {
callbacks []func()
}
func (h *Handler) Register(name string) {
// This closure should NOT be counted as a separate method
callback := func() {
fmt.Println(name)
}
h.callbacks = append(h.callbacks, callback)
}
func (h *Handler) Execute() {
// The func literal here is NOT a method
for _, cb := range h.callbacks {
go func(callback func()) {
callback()
}(cb)
}
}
func (h *Handler) Clear() {
h.callbacks = nil
}
"#;
let path = PathBuf::from("test.go");
let result = parse_source(source, &path).unwrap();
let methods: Vec<_> = result.functions.iter()
.filter(|f| f.qualified_name.contains("Handler"))
.collect();
assert_eq!(
methods.len(),
3,
"Expected 3 methods (Register, Execute, Clear), got {:?}",
methods.iter().map(|f| &f.name).collect::<Vec<_>>()
);
}
#[test]
fn test_interface_methods_counted() {
let source = r#"
package main
type Writer interface {
Write(p []byte) (n int, err error)
Close() error
}
"#;
let path = PathBuf::from("test.go");
let result = parse_source(source, &path).unwrap();
let iface = result.classes.iter()
.find(|c| c.name == "Writer")
.expect("Should find Writer interface");
assert_eq!(
iface.methods.len(),
2,
"Expected 2 interface methods (Write, Close), got {:?}",
iface.methods
);
}
}