car-ast 0.13.0

Tree-sitter AST parsing for code-aware inference
Documentation
use serde::{Deserialize, Serialize};

/// Supported languages for AST parsing.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Language {
    Rust,
    Python,
    TypeScript,
    JavaScript,
    Go,
    CSharp,
    Java,
    C,
    Cpp,
    Ruby,
    Php,
    Swift,
    Kotlin,
    Scala,
    Dart,
    Zig,
    Lua,
    Elixir,
    Haskell,
    R,
    Bash,
    Html,
    Css,
    Json,
}

impl Language {
    /// Detect language from file extension.
    pub fn from_extension(ext: &str) -> Option<Self> {
        match ext {
            "rs" => Some(Self::Rust),
            "py" | "pyi" => Some(Self::Python),
            "ts" | "tsx" => Some(Self::TypeScript),
            "js" | "jsx" | "mjs" | "cjs" => Some(Self::JavaScript),
            "go" => Some(Self::Go),
            "cs" | "csx" => Some(Self::CSharp),
            "java" => Some(Self::Java),
            "c" | "h" => Some(Self::C),
            "cpp" | "cxx" | "cc" | "hpp" | "hxx" | "hh" => Some(Self::Cpp),
            "rb" | "rake" | "gemspec" => Some(Self::Ruby),
            "php" => Some(Self::Php),
            "swift" => Some(Self::Swift),
            "kt" | "kts" => Some(Self::Kotlin),
            "scala" | "sc" => Some(Self::Scala),
            "dart" => Some(Self::Dart),
            "zig" => Some(Self::Zig),
            "lua" => Some(Self::Lua),
            "ex" | "exs" => Some(Self::Elixir),
            "hs" => Some(Self::Haskell),
            "r" | "R" => Some(Self::R),
            "sh" | "bash" | "zsh" => Some(Self::Bash),
            "html" | "htm" => Some(Self::Html),
            "css" => Some(Self::Css),
            "json" => Some(Self::Json),
            _ => None,
        }
    }

    /// Detect language from filename (uses extension).
    pub fn from_filename(filename: &str) -> Option<Self> {
        let ext = filename.rsplit('.').next()?;
        Self::from_extension(ext)
    }
}

/// Byte and line/column span in source.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Span {
    pub start_byte: usize,
    pub end_byte: usize,
    pub start_line: u32,
    pub start_col: u32,
    pub end_line: u32,
    pub end_col: u32,
}

impl Span {
    pub fn from_node(node: &tree_sitter::Node) -> Self {
        let start = node.start_position();
        let end = node.end_position();
        Self {
            start_byte: node.start_byte(),
            end_byte: node.end_byte(),
            start_line: start.row as u32,
            start_col: start.column as u32,
            end_line: end.row as u32,
            end_col: end.column as u32,
        }
    }
}

/// Kind of code symbol.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SymbolKind {
    Function,
    Method,
    Struct,
    Class,
    Enum,
    Trait,
    Interface,
    Import,
    Const,
    Module,
    TypeAlias,
    Impl,
}

/// A parsed code symbol with structural information.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Symbol {
    pub name: String,
    pub kind: SymbolKind,
    pub span: Span,
    /// Signature (everything before the body).
    pub signature: String,
    pub doc_comment: Option<String>,
    /// Enclosing symbol name (e.g., impl block name).
    pub parent: Option<String>,
    /// Nested symbols (methods inside impl, etc.).
    pub children: Vec<Symbol>,
}

/// An import statement.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Import {
    pub path: String,
    pub alias: Option<String>,
    pub span: Span,
}

/// A fully parsed file with symbol table.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ParsedFile {
    pub language: Language,
    /// Top-level symbols.
    pub symbols: Vec<Symbol>,
    pub imports: Vec<Import>,
}

impl ParsedFile {
    /// All symbols flattened (top-level + children).
    pub fn all_symbols(&self) -> Vec<&Symbol> {
        let mut out = Vec::new();
        for sym in &self.symbols {
            out.push(sym);
            for child in &sym.children {
                out.push(child);
            }
        }
        out
    }

    /// Find symbols by exact name.
    pub fn find_symbol(&self, name: &str) -> Vec<&Symbol> {
        self.all_symbols()
            .into_iter()
            .filter(|s| s.name == name)
            .collect()
    }

    /// Find symbols by substring match (case-insensitive).
    pub fn find_symbol_fuzzy(&self, query: &str) -> Vec<&Symbol> {
        let query_lower = query.to_lowercase();
        self.all_symbols()
            .into_iter()
            .filter(|s| s.name.to_lowercase().contains(&query_lower))
            .collect()
    }
}

/// Rich context for a symbol.
#[derive(Debug, Clone)]
pub struct SymbolContext {
    pub symbol: Symbol,
    /// Just the symbol's source code.
    pub source: String,
    /// Byte spans where this symbol is referenced in the same file.
    pub references_in_file: Vec<Span>,
}

/// Structural change between two parsed files.
#[derive(Debug, Clone)]
pub enum SymbolChange {
    Added(Symbol),
    Removed(Symbol),
    Modified {
        old: Symbol,
        new: Symbol,
        signature_changed: bool,
    },
}