debtmap 0.19.1

Code complexity and technical debt analyzer
Documentation
//! Tree-sitter parser integration for Go.

use crate::core::ast::GoAst;
use anyhow::{Context, Result};
use std::path::Path;
use tree_sitter::{Language as TsLanguage, Parser, Tree};

fn get_language() -> TsLanguage {
    tree_sitter_go::LANGUAGE.into()
}

pub fn parse_source(content: &str, path: &Path) -> Result<GoAst> {
    let mut parser = Parser::new();
    let language = get_language();

    parser
        .set_language(&language)
        .context("Failed to set tree-sitter Go language")?;

    let tree = parser
        .parse(content, None)
        .context("Failed to parse Go source")?;

    if has_parse_errors(&tree) {
        anyhow::bail!("Go parse tree contains syntax errors");
    }

    Ok(GoAst {
        tree,
        path: path.to_path_buf(),
        source: content.to_string(),
    })
}

pub fn has_parse_errors(tree: &Tree) -> bool {
    tree.root_node().has_error()
}

pub fn node_text<'a>(node: &tree_sitter::Node, source: &'a str) -> &'a str {
    let start = node.start_byte();
    let end = node.end_byte();
    &source[start..end]
}

pub fn node_line(node: &tree_sitter::Node) -> usize {
    node.start_position().row + 1
}

pub fn node_column(node: &tree_sitter::Node) -> usize {
    node.start_position().column + 1
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::path::PathBuf;

    #[test]
    fn test_parse_package_with_function() {
        let source = "package main\n\nfunc main() {}\n";
        let ast = parse_source(source, &PathBuf::from("main.go")).unwrap();

        assert!(!has_parse_errors(&ast.tree));
        assert_eq!(ast.tree.root_node().kind(), "source_file");
    }

    #[test]
    fn test_parse_methods_imports_structs_and_interfaces() {
        let source = r#"package service

import (
    "context"
    alias "net/http"
)

type Handler struct {
    client *alias.Client
}

type Runner interface {
    Run(context.Context) error
}

func (h *Handler) Serve(ctx context.Context) error {
    return nil
}
"#;

        let ast = parse_source(source, &PathBuf::from("handler.go")).unwrap();

        assert!(!has_parse_errors(&ast.tree));
    }

    #[test]
    fn test_parse_go_generics() {
        let source = r#"package collections

type Number interface {
    ~int | ~float64
}

type Set[T comparable] map[T]struct{}

func Map[T any, U any](items []T, f func(T) U) []U {
    return nil
}

func (s Set[T]) Has(item T) bool {
    _, ok := s[item]
    return ok
}
"#;
        let ast = parse_source(source, &PathBuf::from("collections.go")).unwrap();

        assert!(!has_parse_errors(&ast.tree));
    }

    #[test]
    fn test_parse_generated_comment() {
        let source = "// Code generated by mockgen. DO NOT EDIT.\npackage mock\n";
        let ast = parse_source(source, &PathBuf::from("mock_service.go")).unwrap();

        assert!(!has_parse_errors(&ast.tree));
    }

    #[test]
    fn test_malformed_go_fails() {
        let source = "package main\n\nfunc broken( {\n";
        let result = parse_source(source, &PathBuf::from("broken.go"));

        assert!(result.is_err());
    }

    #[test]
    fn test_node_helpers() {
        let source = "package main\n\nfunc main() {}\n";
        let ast = parse_source(source, &PathBuf::from("main.go")).unwrap();
        let root = ast.tree.root_node();

        assert_eq!(node_text(&root, &ast.source), source);
        assert_eq!(node_line(&root), 1);
        assert_eq!(node_column(&root), 1);
    }
}