Skip to main content

normalize_languages/
ast_grep.rs

1//! ast-grep integration for pattern-based code search.
2//!
3//! This module provides an adapter that allows using ast-grep patterns
4//! with our dynamically loaded tree-sitter grammars.
5
6use ast_grep_core::matcher::PatternBuilder;
7use ast_grep_core::tree_sitter::{LanguageExt, StrDoc, TSLanguage};
8use ast_grep_core::{Language as AstGrepLanguage, Pattern, PatternError};
9
10// Re-export LanguageExt for convenience
11pub use ast_grep_core::tree_sitter::LanguageExt as AstGrepLanguageExt;
12
13/// A dynamically loaded language for ast-grep.
14///
15/// Wraps a tree-sitter Language to implement ast-grep's Language trait,
16/// enabling pattern-based searches with dynamically loaded grammars.
17#[derive(Clone)]
18pub struct DynLang(pub tree_sitter::Language);
19
20impl DynLang {
21    /// Create a new DynLang from a tree-sitter Language.
22    pub fn new(lang: tree_sitter::Language) -> Self {
23        Self(lang)
24    }
25
26    /// Create a pattern from an ast-grep pattern string.
27    pub fn pattern(&self, pattern: &str) -> Result<Pattern, PatternError> {
28        Pattern::try_new(pattern, self.clone())
29    }
30}
31
32impl AstGrepLanguage for DynLang {
33    fn kind_to_id(&self, kind: &str) -> u16 {
34        self.0.id_for_node_kind(kind, true)
35    }
36
37    fn field_to_id(&self, field: &str) -> Option<u16> {
38        self.0.field_id_for_name(field).map(|nz| nz.get())
39    }
40
41    fn build_pattern(&self, builder: &PatternBuilder) -> Result<Pattern, PatternError> {
42        builder.build(|src| StrDoc::try_new(src, self.clone()))
43    }
44}
45
46impl LanguageExt for DynLang {
47    fn get_ts_language(&self) -> TSLanguage {
48        self.0.clone()
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    #[test]
56    fn test_pattern_matching() {
57        use ast_grep_core::tree_sitter::LanguageExt;
58
59        let loader = crate::parsers::grammar_loader();
60        let Some(ts_lang) = loader.get("rust").ok() else {
61            eprintln!("Skipping test: rust grammar not available");
62            return;
63        };
64
65        let lang = DynLang::new(ts_lang);
66        let source = "fn foo() { let x = 1; }";
67
68        let grep = lang.ast_grep(source);
69        let root = grep.root();
70
71        // Test pattern matching - find the identifier "foo"
72        let pattern = lang.pattern("foo").expect("pattern failed");
73        let matches: Vec<_> = root.find_all(&pattern).collect();
74        assert_eq!(matches.len(), 1);
75        assert_eq!(matches[0].text(), "foo");
76
77        // Test pattern with metavariable - find let bindings
78        let pattern = lang.pattern("let $X = $Y").expect("pattern failed");
79        let matches: Vec<_> = root.find_all(&pattern).collect();
80        assert_eq!(matches.len(), 1);
81        assert!(matches[0].text().contains("let x = 1"));
82    }
83}