opengrep 1.1.0

Advanced AST-aware code search tool with tree-sitter parsing and AI integration capabilities
Documentation
//! Tree-sitter AST walker

use super::{AstNode, Position, LanguageConfig, NodeMetadata};
use tree_sitter::{Node, Tree, TreeCursor};

/// AST walker that collects nodes
pub struct AstWalker {
    /// Language configuration
    config: Option<LanguageConfig>,
}

impl AstWalker {
    /// Create a new AST walker
    pub fn new() -> Self {
        Self { config: None }
    }
    
    /// Set language configuration
    pub fn with_language(mut self, lang: &str) -> Self {
        self.config = Some(LanguageConfig::for_language(lang));
        self
    }
    
    /// Walk the tree and collect nodes
    pub fn walk(&mut self, tree: &Tree, source: &str, nodes: &mut Vec<AstNode>) {
        let mut cursor = tree.walk();
        self.walk_cursor(&mut cursor, source, nodes, 0, None);
    }
    
    /// Recursively walk the tree using a cursor
    fn walk_cursor(
        &self,
        cursor: &mut TreeCursor,
        source: &str,
        nodes: &mut Vec<AstNode>,
        depth: usize,
        parent: Option<usize>,
    ) {
        let node = cursor.node();
        
        // Check if this node type should be collected
        if self.should_collect_node(&node) {
            let node_index = nodes.len();
            
            let ast_node = AstNode {
                kind: node.kind().to_string(),
                name: self.extract_node_name(&node, source),
                start: Position {
                    line: node.start_position().row,
                    column: node.start_position().column,
                    offset: node.start_byte(),
                },
                end: Position {
                    line: node.end_position().row,
                    column: node.end_position().column,
                    offset: node.end_byte(),
                },
                range: node.byte_range(),
                depth,
                parent,
                children: Vec::new(),
                metadata: self.extract_node_metadata(&node, source),
            };
            
            nodes.push(ast_node);
            
            // Update parent's children
            if let Some(parent_idx) = parent {
                if let Some(parent_node) = nodes.get_mut(parent_idx) {
                    parent_node.children.push(node_index);
                }
            }
            
            // Recursively walk children
            if cursor.goto_first_child() {
                loop {
                    self.walk_cursor(cursor, source, nodes, depth + 1, Some(node_index));
                    if !cursor.goto_next_sibling() {
                        break;
                    }
                }
                cursor.goto_parent();
            }
        } else {
            // Still walk children even if we don't collect this node
            if cursor.goto_first_child() {
                loop {
                    self.walk_cursor(cursor, source, nodes, depth, parent);
                    if !cursor.goto_next_sibling() {
                        break;
                    }
                }
                cursor.goto_parent();
            }
        }
    }
    
    /// Check if a node should be collected
    fn should_collect_node(&self, node: &Node) -> bool {
        if !node.is_named() {
            return false;
        }
        
        if let Some(config) = &self.config {
            config.scope_types.iter().any(|t| t == &node.kind().to_string()) || 
            config.context_types.iter().any(|t| t == &node.kind().to_string())
        } else {
            // Default: collect common node types
            matches!(
                node.kind(),
                "function" | "method" | "class" | "struct" | "interface" |
                "function_declaration" | "function_definition" | 
                "method_definition" | "class_declaration" | "class_definition" |
                "impl_item" | "function_item" | "struct_item" | "enum_item" |
                "trait_item" | "mod_item"
            )
        }
    }
    
    /// Extract the name of a node
    fn extract_node_name(&self, node: &Node, source: &str) -> Option<String> {
        let name_fields: Vec<&str> = if let Some(config) = &self.config {
            config.name_fields.iter().map(|s| s.as_str()).collect()
        } else {
            vec!["name", "identifier"]
        };
        
        for field in name_fields {
            if let Some(name_node) = node.child_by_field_name(&field) {
                let name = &source[name_node.byte_range()];
                return Some(name.to_string());
            }
        }
        
        // Try to find identifier by walking children
        let mut cursor = node.walk();
        if cursor.goto_first_child() {
            loop {
                let child = cursor.node();
                if child.kind() == "identifier" {
                    let name = &source[child.byte_range()];
                    return Some(name.to_string());
                }
                if !cursor.goto_next_sibling() {
                    break;
                }
            }
        }
        
        None
    }
    
    /// Extract metadata for a node
    fn extract_node_metadata(&self, node: &Node, source: &str) -> NodeMetadata {
        let mut metadata = NodeMetadata::default();
        
        if let Some(config) = &self.config {
            metadata.is_scope = config.scope_types.iter().any(|t| t == &node.kind().to_string());
            metadata.is_definition = config.definition_types.iter().any(|t| t == &node.kind().to_string());
            metadata.is_declaration = config.declaration_types.iter().any(|t| t == &node.kind().to_string());
        } else {
            metadata.is_scope = self.is_default_scope_node(node);
            metadata.is_definition = self.is_default_definition_node(node);
            metadata.is_declaration = self.is_default_declaration_node(node);
        }
        
        // Extract visibility and documentation
        metadata.visibility = self.extract_visibility(node, source);
        
        // Extract documentation comment
        metadata.documentation = self.extract_documentation(node, source);
        
        metadata
    }
    
    /// Check if a node is a scope by default rules
    fn is_default_scope_node(&self, node: &Node) -> bool {
        matches!(
            node.kind(),
            "function" | "method" | "class" | "struct" | "interface" |
            "function_declaration" | "function_definition" | 
            "method_definition" | "class_declaration" | "class_definition" |
            "impl_item" | "function_item" | "struct_item" | "enum_item" |
            "trait_item" | "mod_item"
        )
    }
    
    /// Check if a node is a definition by default rules
    fn is_default_definition_node(&self, node: &Node) -> bool {
        matches!(
            node.kind(),
            "function_definition" | "class_definition" | "struct_item" |
            "enum_item" | "trait_item" | "function_item"
        )
    }
    
    /// Check if a node is a declaration by default rules
    fn is_default_declaration_node(&self, node: &Node) -> bool {
        matches!(
            node.kind(),
            "function_declaration" | "class_declaration" | "function_signature"
        )
    }
    
    /// Extract visibility modifier
    fn extract_visibility(&self, _node: &Node, _source: &str) -> Option<String> {
        // TODO: Implement visibility extraction based on language
        None
    }
    
    /// Extract documentation comment
    fn extract_documentation(&self, _node: &Node, _source: &str) -> Option<String> {
        // TODO: Implement documentation extraction
        None
    }
}

impl Default for AstWalker {
    fn default() -> Self {
        Self::new()
    }
}