car-ast 0.1.0

Tree-sitter AST parsing for code-aware inference
Documentation
// JavaScript extraction reuses TypeScript logic — the grammars are nearly identical
// for the node types we care about (function_declaration, class_declaration, etc.)

use crate::types::*;
use super::{node_text, field_text, extract_doc_comment, extract_signature};

pub fn extract(tree: &tree_sitter::Tree, source: &[u8]) -> (Vec<Symbol>, Vec<Import>) {
    let root = tree.root_node();
    let mut symbols = Vec::new();
    let mut imports = Vec::new();
    let mut cursor = root.walk();

    for child in root.children(&mut cursor) {
        match child.kind() {
            "function_declaration" => {
                if let Some(sym) = extract_function(&child, source, None) {
                    symbols.push(sym);
                }
            }
            "class_declaration" => {
                if let Some(sym) = extract_class(&child, source) {
                    symbols.push(sym);
                }
            }
            "import_statement" => {
                imports.push(Import {
                    path: node_text(&child, source).to_string(),
                    alias: None,
                    span: Span::from_node(&child),
                });
            }
            "export_statement" => {
                let mut inner = child.walk();
                for grandchild in child.children(&mut inner) {
                    match grandchild.kind() {
                        "function_declaration" => {
                            if let Some(sym) = extract_function(&grandchild, source, None) {
                                symbols.push(sym);
                            }
                        }
                        "class_declaration" => {
                            if let Some(sym) = extract_class(&grandchild, source) {
                                symbols.push(sym);
                            }
                        }
                        _ => {}
                    }
                }
            }
            "lexical_declaration" => {
                extract_lexical_declarations(&child, source, &mut symbols);
            }
            _ => {}
        }
    }

    (symbols, imports)
}

fn extract_function(node: &tree_sitter::Node, source: &[u8], parent: Option<&str>) -> Option<Symbol> {
    let name = field_text(node, "name", source)?;
    Some(Symbol {
        name: name.to_string(),
        kind: if parent.is_some() { SymbolKind::Method } else { SymbolKind::Function },
        span: Span::from_node(node),
        signature: extract_signature(node, "body", source),
        doc_comment: extract_doc_comment(node, source),
        parent: parent.map(|s| s.to_string()),
        children: Vec::new(),
    })
}

fn extract_class(node: &tree_sitter::Node, source: &[u8]) -> Option<Symbol> {
    let name = field_text(node, "name", source)?;
    let mut methods = Vec::new();

    if let Some(body) = node.child_by_field_name("body") {
        let mut cursor = body.walk();
        for child in body.children(&mut cursor) {
            if child.kind() == "method_definition" {
                if let Some(mname) = field_text(&child, "name", source) {
                    methods.push(Symbol {
                        name: mname.to_string(),
                        kind: SymbolKind::Method,
                        span: Span::from_node(&child),
                        signature: extract_signature(&child, "body", source),
                        doc_comment: extract_doc_comment(&child, source),
                        parent: Some(name.to_string()),
                        children: Vec::new(),
                    });
                }
            }
        }
    }

    Some(Symbol {
        name: name.to_string(),
        kind: SymbolKind::Class,
        span: Span::from_node(node),
        signature: extract_signature(node, "body", source),
        doc_comment: extract_doc_comment(node, source),
        parent: None,
        children: methods,
    })
}

fn extract_lexical_declarations(node: &tree_sitter::Node, source: &[u8], symbols: &mut Vec<Symbol>) {
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        if child.kind() == "variable_declarator" {
            if let Some(name) = field_text(&child, "name", source) {
                if let Some(value) = child.child_by_field_name("value") {
                    let kind = value.kind();
                    if kind == "arrow_function" || kind == "function" {
                        symbols.push(Symbol {
                            name: name.to_string(),
                            kind: SymbolKind::Function,
                            span: Span::from_node(node),
                            signature: extract_signature(node, "body", source),
                            doc_comment: extract_doc_comment(node, source),
                            parent: None,
                            children: Vec::new(),
                        });
                    } else if name.chars().all(|c| c.is_uppercase() || c == '_') {
                        symbols.push(Symbol {
                            name: name.to_string(),
                            kind: SymbolKind::Const,
                            span: Span::from_node(node),
                            signature: node_text(node, source).to_string(),
                            doc_comment: extract_doc_comment(node, source),
                            parent: None,
                            children: Vec::new(),
                        });
                    }
                }
            }
        }
    }
}