car-ast 0.1.0

Tree-sitter AST parsing for code-aware inference
Documentation
#[cfg(feature = "rust")]
pub mod rust;
#[cfg(feature = "python")]
pub mod python;
#[cfg(feature = "typescript")]
pub mod typescript;
#[cfg(feature = "javascript")]
pub mod javascript;
#[cfg(feature = "go")]
pub mod go;

use crate::types::*;

/// Extract symbols from a tree-sitter tree for the given language.
pub fn extract_symbols(lang: Language, tree: &tree_sitter::Tree, source: &[u8]) -> (Vec<Symbol>, Vec<Import>) {
    match lang {
        #[cfg(feature = "rust")]
        Language::Rust => rust::extract(tree, source),
        #[cfg(feature = "python")]
        Language::Python => python::extract(tree, source),
        #[cfg(feature = "typescript")]
        Language::TypeScript => typescript::extract(tree, source),
        #[cfg(feature = "javascript")]
        Language::JavaScript => javascript::extract(tree, source),
        #[cfg(feature = "go")]
        Language::Go => go::extract(tree, source),
        #[allow(unreachable_patterns)]
        _ => (Vec::new(), Vec::new()),
    }
}

/// Get the tree-sitter Language for a Language enum value.
pub fn ts_language(lang: Language) -> Option<tree_sitter::Language> {
    match lang {
        #[cfg(feature = "rust")]
        Language::Rust => Some(tree_sitter_rust::LANGUAGE.into()),
        #[cfg(feature = "python")]
        Language::Python => Some(tree_sitter_python::LANGUAGE.into()),
        #[cfg(feature = "typescript")]
        Language::TypeScript => Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
        #[cfg(feature = "javascript")]
        Language::JavaScript => Some(tree_sitter_javascript::LANGUAGE.into()),
        #[cfg(feature = "go")]
        Language::Go => Some(tree_sitter_go::LANGUAGE.into()),
        #[allow(unreachable_patterns)]
        _ => None,
    }
}

/// Helper: get text from a node.
pub(crate) fn node_text<'a>(node: &tree_sitter::Node, source: &'a [u8]) -> &'a str {
    node.utf8_text(source).unwrap_or("")
}

/// Helper: find child by field name and get its text.
pub(crate) fn field_text<'a>(node: &tree_sitter::Node, field: &str, source: &'a [u8]) -> Option<&'a str> {
    node.child_by_field_name(field).map(|n| node_text(&n, source))
}

/// Helper: extract doc comment from preceding sibling nodes.
pub(crate) fn extract_doc_comment(node: &tree_sitter::Node, source: &[u8]) -> Option<String> {
    let mut comments = Vec::new();
    let mut sibling = node.prev_sibling();
    while let Some(s) = sibling {
        let kind = s.kind();
        if kind == "line_comment" || kind == "comment" || kind == "block_comment" {
            let text = node_text(&s, source);
            // Only doc comments (/// or /** or # for python)
            if text.starts_with("///") || text.starts_with("/**") || text.starts_with("## ") {
                comments.push(text.to_string());
            } else {
                break;
            }
        } else if kind == "attribute_item" || kind == "decorator" {
            // Skip attributes/decorators, keep looking for comments
        } else {
            break;
        }
        sibling = s.prev_sibling();
    }
    if comments.is_empty() {
        None
    } else {
        comments.reverse();
        Some(comments.join("\n"))
    }
}

/// Helper: extract signature (text up to body start).
pub(crate) fn extract_signature(node: &tree_sitter::Node, body_field: &str, source: &[u8]) -> String {
    if let Some(body) = node.child_by_field_name(body_field) {
        let sig_end = body.start_byte();
        let sig = &source[node.start_byte()..sig_end];
        std::str::from_utf8(sig).unwrap_or("").trim().to_string()
    } else {
        node_text(node, source).to_string()
    }
}