context_creator/core/semantic/languages/
rust.rs

1//! Semantic analyzer for Rust
2
3use crate::core::semantic::{
4    analyzer::{AnalysisResult, LanguageAnalyzer, SemanticContext, SemanticResult},
5    path_validator::{validate_import_path, validate_module_name},
6    query_engine::QueryEngine,
7    resolver::{ModuleResolver, ResolvedPath, ResolverUtils},
8};
9use crate::utils::error::ContextCreatorError;
10use std::path::Path;
11use tree_sitter::Parser;
12
13#[allow(clippy::new_without_default)]
14pub struct RustAnalyzer {
15    query_engine: QueryEngine,
16}
17
18impl RustAnalyzer {
19    pub fn new() -> Self {
20        let language = tree_sitter_rust::language();
21        let query_engine =
22            QueryEngine::new(language, "rust").expect("Failed to create Rust query engine");
23        Self { query_engine }
24    }
25}
26
27impl LanguageAnalyzer for RustAnalyzer {
28    fn language_name(&self) -> &'static str {
29        "Rust"
30    }
31
32    fn analyze_file(
33        &self,
34        path: &Path,
35        content: &str,
36        context: &SemanticContext,
37    ) -> SemanticResult<AnalysisResult> {
38        let mut parser = Parser::new();
39        parser
40            .set_language(tree_sitter_rust::language())
41            .map_err(|e| ContextCreatorError::ParseError(format!("Failed to set language: {e}")))?;
42
43        let mut result = self
44            .query_engine
45            .analyze_with_parser(&mut parser, content)?;
46
47        // Correlate type references with imports to populate module information
48        self.correlate_types_with_imports(&mut result);
49
50        // Resolve type definitions for the type references found
51        self.query_engine.resolve_type_definitions(
52            &mut result.type_references,
53            path,
54            &context.base_dir,
55        )?;
56
57        Ok(result)
58    }
59
60    fn can_handle_extension(&self, extension: &str) -> bool {
61        extension == "rs"
62    }
63
64    fn supported_extensions(&self) -> Vec<&'static str> {
65        vec!["rs"]
66    }
67}
68
69impl RustAnalyzer {
70    /// Correlate type references with imports to populate module information
71    fn correlate_types_with_imports(&self, result: &mut AnalysisResult) {
72        use std::collections::HashMap;
73
74        // Create a mapping from imported type names to their module paths
75        let mut type_to_module: HashMap<String, String> = HashMap::new();
76
77        for import in &result.imports {
78            if import.items.is_empty() {
79                // Handle simple imports like "use std::collections::HashMap;"
80                if let Some(type_name) = import.module.split("::").last() {
81                    // Check if this looks like a type (starts with uppercase)
82                    if type_name.chars().next().is_some_and(|c| c.is_uppercase()) {
83                        type_to_module.insert(type_name.to_string(), import.module.clone());
84                    }
85                }
86            } else {
87                // Handle scoped imports like "use model::{Account, DatabaseFactory};"
88                for item in &import.items {
89                    // Check if this looks like a type (starts with uppercase)
90                    if item.chars().next().is_some_and(|c| c.is_uppercase()) {
91                        type_to_module.insert(item.clone(), import.module.clone());
92                    }
93                }
94            }
95        }
96
97        // Update type references with module information
98        for type_ref in &mut result.type_references {
99            if type_ref.module.is_none() {
100                if let Some(module_path) = type_to_module.get(&type_ref.name) {
101                    type_ref.module = Some(module_path.clone());
102                }
103            }
104        }
105    }
106}
107
108pub struct RustModuleResolver;
109
110impl ModuleResolver for RustModuleResolver {
111    fn resolve_import(
112        &self,
113        module_path: &str,
114        from_file: &Path,
115        base_dir: &Path,
116    ) -> Result<ResolvedPath, ContextCreatorError> {
117        // Validate module name for security
118        validate_module_name(module_path)?;
119
120        // Handle standard library imports
121        if self.is_external_module(module_path) {
122            return Ok(ResolvedPath {
123                path: base_dir.join("Cargo.toml"), // Point to Cargo.toml as indicator
124                is_external: true,
125                confidence: 1.0,
126            });
127        }
128
129        // Handle crate-relative imports
130        if module_path.starts_with("crate::") {
131            let relative_path = module_path.strip_prefix("crate::").unwrap();
132            let path = ResolverUtils::module_to_path(relative_path);
133            let full_path = base_dir.join("src").join(path);
134
135            if let Some(resolved) = ResolverUtils::find_with_extensions(&full_path, &["rs"]) {
136                let validated_path = validate_import_path(base_dir, &resolved)?;
137                return Ok(ResolvedPath {
138                    path: validated_path,
139                    is_external: false,
140                    confidence: 0.9,
141                });
142            }
143
144            // Try as a directory module (mod.rs)
145            let mod_path = full_path.join("mod.rs");
146            if mod_path.exists() {
147                let validated_path = validate_import_path(base_dir, &mod_path)?;
148                return Ok(ResolvedPath {
149                    path: validated_path,
150                    is_external: false,
151                    confidence: 0.9,
152                });
153            }
154        }
155
156        // Handle relative imports (self, super)
157        if module_path.starts_with("self::") || module_path.starts_with("super::") {
158            if let Some(resolved) = ResolverUtils::resolve_relative(module_path, from_file, &["rs"])
159            {
160                return Ok(ResolvedPath {
161                    path: resolved,
162                    is_external: false,
163                    confidence: 0.9,
164                });
165            }
166        }
167
168        // Handle simple module names (e.g., "mod lib;" in same directory)
169        if !module_path.contains("::") {
170            if let Some(parent) = from_file.parent() {
171                // Try as a file
172                let file_path = parent.join(format!("{module_path}.rs"));
173                if file_path.exists() {
174                    let validated_path = validate_import_path(base_dir, &file_path)?;
175                    return Ok(ResolvedPath {
176                        path: validated_path,
177                        is_external: false,
178                        confidence: 0.9,
179                    });
180                }
181
182                // Try as a directory module
183                let mod_path = parent.join(module_path).join("mod.rs");
184                if mod_path.exists() {
185                    let validated_path = validate_import_path(base_dir, &mod_path)?;
186                    return Ok(ResolvedPath {
187                        path: validated_path,
188                        is_external: false,
189                        confidence: 0.9,
190                    });
191                }
192            }
193        }
194
195        // Otherwise, assume it's an external crate
196        Ok(ResolvedPath {
197            path: base_dir.join("Cargo.toml"), // Point to Cargo.toml as indicator
198            is_external: true,
199            confidence: 0.5,
200        })
201    }
202
203    fn get_file_extensions(&self) -> Vec<&'static str> {
204        vec!["rs"]
205    }
206
207    fn is_external_module(&self, module_path: &str) -> bool {
208        // Common standard library crates
209        let stdlib_crates = ["std", "core", "alloc", "proc_macro", "test"];
210
211        // Get the first part of the path (before ::)
212        let first_part = module_path.split("::").next().unwrap_or(module_path);
213
214        // Check if it's a standard library crate
215        if stdlib_crates.contains(&first_part) {
216            return true;
217        }
218
219        // Simple module names (no ::) are NOT external - they're local modules
220        if !module_path.contains("::") {
221            return false;
222        }
223
224        // crate::, self::, super:: are always local
225        if module_path.starts_with("crate::")
226            || module_path.starts_with("self::")
227            || module_path.starts_with("super::")
228        {
229            return false;
230        }
231
232        // Other paths with :: might be external crates
233        // For now, we'll consider them external unless we have more context
234        true
235    }
236}