reflex/parsers/
mod.rs

1//! Tree-sitter parsers for extracting symbols from source code
2//!
3//! This module provides language-specific parsers that extract symbols
4//! (functions, classes, structs, etc.) from source code using Tree-sitter.
5//!
6//! Each language has its own submodule with a `parse` function that takes
7//! source code and returns a vector of symbols.
8
9pub mod rust;
10pub mod typescript;
11pub mod tsconfig;
12pub mod vue;
13pub mod svelte;
14pub mod php;
15pub mod python;
16pub mod go;
17pub mod java;
18pub mod c;
19pub mod cpp;
20pub mod csharp;
21pub mod ruby;
22pub mod kotlin;
23// pub mod swift;  // Temporarily disabled - requires tree-sitter 0.23
24pub mod zig;
25
26use anyhow::{anyhow, Result};
27use crate::models::{Language, SearchResult};
28
29/// Parser factory that selects the appropriate parser based on language
30pub struct ParserFactory;
31
32/// Extracted import/dependency information (before file ID resolution)
33#[derive(Debug, Clone)]
34pub struct ImportInfo {
35    /// Import path as written in source code
36    pub imported_path: String,
37    /// Type classification hint (internal/external/stdlib)
38    pub import_type: crate::models::ImportType,
39    /// Line number where import appears
40    pub line_number: usize,
41    /// Imported symbols (for selective imports like `from x import a, b`)
42    pub imported_symbols: Option<Vec<String>>,
43}
44
45/// Extracted export/re-export information (for barrel export tracking)
46#[derive(Debug, Clone)]
47pub struct ExportInfo {
48    /// Symbol being exported (None for wildcard `export * from`)
49    pub exported_symbol: Option<String>,
50    /// Source path where the symbol is re-exported from
51    pub source_path: String,
52    /// Line number where export appears
53    pub line_number: usize,
54}
55
56/// Trait for extracting dependencies from source code
57///
58/// Each language parser can implement this trait to extract import/include
59/// statements from source files.
60pub trait DependencyExtractor {
61    /// Extract all imports/dependencies from source code
62    ///
63    /// Returns a list of ImportInfo records (before file ID resolution).
64    /// The indexer will resolve these to file IDs and store in the database.
65    ///
66    /// # Arguments
67    ///
68    /// * `source` - Source code content
69    ///
70    /// # Returns
71    ///
72    /// Vector of ImportInfo records, or an error if parsing fails
73    fn extract_dependencies(source: &str) -> Result<Vec<ImportInfo>>;
74}
75
76impl ParserFactory {
77    /// Get the tree-sitter grammar for a language
78    ///
79    /// This is the single source of truth for tree-sitter language grammars.
80    /// Used by both symbol parsers and AST query matching.
81    ///
82    /// Returns an error for:
83    /// - Vue/Svelte (use line-based parsing instead of tree-sitter)
84    /// - Swift (temporarily disabled due to tree-sitter version incompatibility)
85    /// - Unknown languages
86    pub fn get_language_grammar(language: Language) -> Result<tree_sitter::Language> {
87        match language {
88            Language::Rust => Ok(tree_sitter_rust::LANGUAGE.into()),
89            Language::Python => Ok(tree_sitter_python::LANGUAGE.into()),
90            Language::TypeScript => Ok(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
91            Language::JavaScript => Ok(tree_sitter_typescript::LANGUAGE_TSX.into()),
92            Language::Go => Ok(tree_sitter_go::LANGUAGE.into()),
93            Language::Java => Ok(tree_sitter_java::LANGUAGE.into()),
94            Language::C => Ok(tree_sitter_c::LANGUAGE.into()),
95            Language::Cpp => Ok(tree_sitter_cpp::LANGUAGE.into()),
96            Language::CSharp => Ok(tree_sitter_c_sharp::LANGUAGE.into()),
97            Language::PHP => Ok(tree_sitter_php::LANGUAGE_PHP.into()),
98            Language::Ruby => Ok(tree_sitter_ruby::LANGUAGE.into()),
99            Language::Kotlin => Ok(tree_sitter_kotlin_ng::LANGUAGE.into()),
100            Language::Zig => Ok(tree_sitter_zig::LANGUAGE.into()),
101            Language::Swift => Err(anyhow!(
102                "Swift support temporarily disabled (requires tree-sitter 0.23)"
103            )),
104            Language::Vue => Err(anyhow!(
105                "Vue uses line-based parsing, not tree-sitter (tree-sitter-vue incompatible with tree-sitter 0.24+)"
106            )),
107            Language::Svelte => Err(anyhow!(
108                "Svelte uses line-based parsing, not tree-sitter (tree-sitter-svelte incompatible with tree-sitter 0.24+)"
109            )),
110            Language::Unknown => Err(anyhow!("Unknown language")),
111        }
112    }
113
114    /// Get language keywords that should trigger "list all symbols" behavior
115    ///
116    /// When a user searches for a keyword (like "class", "function") with --symbols,
117    /// we interpret it as "list all symbols of that type" rather than looking for
118    /// a symbol literally named "class" or "function".
119    ///
120    /// Returns an empty slice for languages without common keywords or unsupported languages.
121    pub fn get_keywords(language: Language) -> &'static [&'static str] {
122        match language {
123            Language::Rust => &["fn", "struct", "enum", "trait", "impl", "mod", "const", "static", "type", "macro"],
124            Language::PHP => &["class", "function", "trait", "interface", "enum"],
125            Language::Python => &["class", "def", "async"],
126            Language::TypeScript | Language::JavaScript => &["class", "function", "interface", "type", "enum", "const", "let", "var"],
127            Language::Go => &["func", "struct", "interface", "type", "const", "var"],
128            Language::Java => &["class", "interface", "enum", "@interface"],
129            Language::C => &["struct", "enum", "union", "typedef"],
130            Language::Cpp => &["class", "struct", "enum", "union", "typedef", "namespace", "template"],
131            Language::CSharp => &["class", "struct", "interface", "enum", "delegate", "record", "namespace"],
132            Language::Ruby => &["class", "module", "def"],
133            Language::Kotlin => &["class", "fun", "interface", "object", "enum", "annotation"],
134            Language::Zig => &["fn", "struct", "enum", "const", "var", "type"],
135            Language::Swift => &["class", "struct", "enum", "protocol", "func", "var", "let"],
136            Language::Vue | Language::Svelte => &["function", "const", "let", "var"],
137            Language::Unknown => &[],
138        }
139    }
140
141    /// Get all keywords across all supported languages
142    ///
143    /// Returns a deduplicated union of keywords from all languages.
144    /// Used for keyword detection when --lang is not specified.
145    ///
146    /// When a user searches for a keyword with --symbols or --kind,
147    /// we enable keyword mode regardless of language filter.
148    pub fn get_all_keywords() -> &'static [&'static str] {
149        &[
150            // Functions
151            "fn", "function", "def", "func",
152            // Classes and types
153            "class", "struct", "enum", "interface", "trait", "type", "record",
154            // Modules and namespaces
155            "mod", "module", "namespace",
156            // Variables and constants
157            "const", "static", "let", "var",
158            // Other constructs
159            "impl", "async", "object", "annotation", "protocol",
160            "union", "typedef", "delegate", "template",
161            // Java annotations
162            "@interface",
163        ]
164    }
165
166    /// Parse a file and extract symbols based on its language
167    pub fn parse(
168        path: &str,
169        source: &str,
170        language: Language,
171    ) -> Result<Vec<SearchResult>> {
172        match language {
173            Language::Rust => rust::parse(path, source),
174            Language::TypeScript => typescript::parse(path, source, language),
175            Language::JavaScript => typescript::parse(path, source, language),
176            Language::Vue => vue::parse(path, source),
177            Language::Svelte => svelte::parse(path, source),
178            Language::Python => python::parse(path, source),
179            Language::Go => go::parse(path, source),
180            Language::Java => java::parse(path, source),
181            Language::PHP => php::parse(path, source),
182            Language::C => c::parse(path, source),
183            Language::Cpp => cpp::parse(path, source),
184            Language::CSharp => csharp::parse(path, source),
185            Language::Ruby => ruby::parse(path, source),
186            Language::Kotlin => kotlin::parse(path, source),
187            Language::Swift => {
188                log::warn!("Swift support temporarily disabled (requires tree-sitter 0.23): {}", path);
189                Ok(vec![])
190            }
191            Language::Zig => zig::parse(path, source),
192            Language::Unknown => {
193                log::warn!("Unknown language for file: {}", path);
194                Ok(vec![])
195            }
196        }
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203
204    #[test]
205    fn test_parser_factory() {
206        // Simple test to ensure module compiles
207        let _factory = ParserFactory;
208    }
209}