use crate::{languages::get_tree_sitter_language, CodeConstruct, Error, Language, ParsedFile};
use regex::Regex;
use tree_sitter::{Query, QueryCursor};
use streaming_iterator::StreamingIterator;
pub fn search_by_node_type(
parsed_file: &ParsedFile,
node_type: &str,
name_pattern: Option<&str>,
) -> Vec<CodeConstruct> {
let mut results = Vec::new();
let regex = if let Some(pattern) = name_pattern {
match Regex::new(pattern) {
Ok(r) => Some(r),
Err(_) => return results, }
} else {
None
};
for construct in &parsed_file.constructs {
if construct.node_type == node_type {
if let Some(ref regex) = regex {
if let Some(ref name) = construct.name {
if regex.is_match(name) {
results.push(construct.clone());
}
}
} else {
results.push(construct.clone());
}
}
}
results
}
pub fn search_by_multiple_node_types(
parsed_file: &ParsedFile,
node_types: &[&str],
name_pattern: Option<&str>,
) -> Vec<CodeConstruct> {
let mut results = Vec::new();
let regex = if let Some(pattern) = name_pattern {
match Regex::new(pattern) {
Ok(r) => Some(r),
Err(_) => return results, }
} else {
None
};
for construct in &parsed_file.constructs {
if node_types.contains(&construct.node_type.as_str()) {
if let Some(ref regex) = regex {
if let Some(ref name) = construct.name {
if regex.is_match(name) {
results.push(construct.clone());
}
}
} else {
results.push(construct.clone());
}
}
}
results
}
pub fn search_by_query(
parsed_file: &ParsedFile,
tree_sitter_query: &str,
) -> Result<Vec<CodeConstruct>, Error> {
let mut results = Vec::new();
let tree = parsed_file.syntax_tree.as_ref()
.ok_or_else(|| Error::Parse("No syntax tree available".to_string()))?;
let ts_language = get_tree_sitter_language(&parsed_file.language)?;
let query = Query::new(&ts_language, tree_sitter_query)
.map_err(|e| Error::InvalidQuery(e.to_string()))?;
let mut cursor = QueryCursor::new();
let source = std::fs::read_to_string(&parsed_file.file_path)
.map_err(|e| Error::Io(e.to_string()))?;
let mut matches = cursor.matches(&query, tree.root_node(), source.as_bytes());
while let Some(query_match) = matches.next() {
for capture in query_match.captures {
let node = capture.node;
let construct = create_code_construct_from_node(node, &source, &parsed_file.language);
results.push(construct);
}
}
Ok(results)
}
fn create_code_construct_from_node(
node: tree_sitter::Node,
source: &str,
_language: &Language,
) -> CodeConstruct {
let start_byte = node.start_byte();
let end_byte = node.end_byte();
let source_code = source[start_byte..end_byte].to_string();
let start_point = node.start_position();
let end_point = node.end_position();
let name = extract_node_name(node, source);
CodeConstruct {
node_type: node.kind().to_string(),
name,
source_code,
start_line: start_point.row + 1, end_line: end_point.row + 1,
start_byte,
end_byte,
parent: None,
children: Vec::new(),
metadata: crate::ConstructMetadata {
visibility: None,
modifiers: Vec::new(),
parameters: Vec::new(),
return_type: None,
inheritance: Vec::new(),
annotations: Vec::new(),
documentation: None,
},
}
}
fn extract_node_name(node: tree_sitter::Node, source: &str) -> Option<String> {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "identifier" || child.kind() == "name" {
let start = child.start_byte();
let end = child.end_byte();
return Some(source[start..end].to_string());
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{parse_file, Language};
use std::fs;
use tokio;
#[tokio::test]
async fn test_no_duplicate_results() {
let test_content = r#"
class CacheEngine:
def __init__(self):
pass
def _allocate_kv_cache(self):
return "cache allocated"
class InnerClass:
def _allocate_kv_cache(self):
return "inner cache"
"#;
let test_file = "test_cache_duplication.py";
fs::write(test_file, test_content).expect("Failed to write test file");
let parsed = parse_file(test_file, Language::Python).await.expect("Failed to parse file");
let functions = search_by_node_type(&parsed, "function_definition", Some("_allocate_kv_cache"));
assert_eq!(functions.len(), 2, "Expected exactly 2 functions, but found {}", functions.len());
let mut parent_names = Vec::new();
for func in &functions {
if let Some(parent) = &func.parent {
if let Some(parent_name) = &parent.name {
parent_names.push(parent_name.clone());
}
}
}
parent_names.sort();
parent_names.dedup();
assert_eq!(parent_names.len(), 2, "Expected 2 different parent classes");
fs::remove_file(test_file).ok();
}
}