opengrep 1.1.0

Advanced AST-aware code search tool with tree-sitter parsing and AI integration capabilities
Documentation
use opengrep::{Config, SearchEngine};
use std::fs::{self, File};
use std::io::Write;
use std::path::PathBuf;
use tempfile::tempdir;

fn setup_test_files() -> (PathBuf, Vec<PathBuf>) {
    let dir = tempdir().unwrap();
    let file1_path = dir.path().join("file1.rs");
    let file2_path = dir.path().join("file2.py");
    let nested_dir = dir.path().join("nested");
    fs::create_dir(&nested_dir).unwrap();
    let file3_path = nested_dir.join("file3.js");

    let mut file1 = File::create(&file1_path).unwrap();
    file1.write_all(b"fn main() {\n    // A TODO comment\n}\n").unwrap();

    let mut file2 = File::create(&file2_path).unwrap();
    file2.write_all(b"def main():\n    # Another TODO here\n    pass\n").unwrap();

    let mut file3 = File::create(&file3_path).unwrap();
    file3.write_all(b"function main() {\n    // No items of interest\n}\n").unwrap();

    (dir.into_path(), vec![file1_path, file2_path, file3_path])
}

#[tokio::test]
async fn test_simple_search() {
    let (dir_path, _files) = setup_test_files();
    let config = Config::default();
    let engine = SearchEngine::new(config);

    let results = engine.search("TODO", &[dir_path]).await.unwrap();

    assert_eq!(results.len(), 2);
    let total_matches: usize = results.iter().map(|r| r.matches.len()).sum();
    assert_eq!(total_matches, 2);
}

#[tokio::test]
async fn test_case_sensitive_search() {
    let (dir_path, _files) = setup_test_files();
    let mut config = Config::default();
    config.search.ignore_case = false;
    let engine = SearchEngine::new(config);

    let results = engine.search("todo", &[dir_path]).await.unwrap();

    assert_eq!(results.len(), 0);
}

#[tokio::test]
async fn test_regex_search() {
    let (dir_path, _files) = setup_test_files();
    let mut config = Config::default();
    config.search.regex = true;
    let engine = SearchEngine::new(config);

    let results = engine.search("co..ent", &[dir_path]).await.unwrap();

    assert_eq!(results.len(), 1);
    assert_eq!(results[0].matches.len(), 1);
    assert!(results[0].matches[0].line.contains("comment"));
}

#[tokio::test]
async fn test_ast_context_search() {
    let (dir_path, _files) = setup_test_files();
    let mut config = Config::default();
    config.output.show_ast_context = true;
    let engine = SearchEngine::new(config);

    let results = engine.search("TODO", &[dir_path]).await.unwrap();

    assert_eq!(results.len(), 2);
    let rust_match = results.iter().find(|r| r.path.ends_with("file1.rs")).unwrap();
    assert_eq!(rust_match.matches[0].ast_context.as_ref().unwrap().parent_name, "main");
}