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            } else if let Some(ref existing_module) = type_ref.module {
104                // Check if the module path ends with the type name (e.g., "crate::domain::Session" for type "Session")
105                // This happens when scoped_type_identifier captures the full path
106                if existing_module.ends_with(&format!("::{}", type_ref.name)) {
107                    // Remove the redundant type name from the module path
108                    let corrected_module = existing_module
109                        .strip_suffix(&format!("::{}", type_ref.name))
110                        .unwrap_or(existing_module);
111                    type_ref.module = Some(corrected_module.to_string());
112                }
113            }
114        }
115    }
116}
117
118pub struct RustModuleResolver;
119
120impl ModuleResolver for RustModuleResolver {
121    fn resolve_import(
122        &self,
123        module_path: &str,
124        from_file: &Path,
125        base_dir: &Path,
126    ) -> Result<ResolvedPath, ContextCreatorError> {
127        tracing::debug!(
128            "RustModuleResolver::resolve_import - module: '{}', from_file: {}, base_dir: {}",
129            module_path,
130            from_file.display(),
131            base_dir.display()
132        );
133
134        // Validate module name for security
135        validate_module_name(module_path)?;
136
137        // Handle current crate imports FIRST (e.g., my_lib::module)
138        // Check if this might be the current crate by looking for Cargo.toml
139        let cargo_path = base_dir.join("Cargo.toml");
140        tracing::debug!(
141            "Checking for Cargo.toml at: {}, exists: {}",
142            cargo_path.display(),
143            cargo_path.exists()
144        );
145        if cargo_path.exists() {
146            // Try to parse crate name from Cargo.toml
147            if let Ok(contents) = std::fs::read_to_string(&cargo_path) {
148                // Simple parsing to find package name
149                for line in contents.lines() {
150                    let trimmed = line.trim();
151                    if trimmed.starts_with("name") && trimmed.contains('=') {
152                        // Extract the crate name from: name = "my_lib"
153                        if let Some(name_part) = trimmed.split('=').nth(1) {
154                            let crate_name = name_part.trim().trim_matches('"').trim_matches('\'');
155                            tracing::debug!(
156                                "Found crate name: '{}', checking against module path: '{}'",
157                                crate_name,
158                                module_path
159                            );
160                            if module_path.starts_with(&format!("{crate_name}::")) {
161                                // This is a reference to the current crate - treat it like crate::
162                                let relative_path = module_path
163                                    .strip_prefix(&format!("{crate_name}::"))
164                                    .unwrap();
165
166                                // IMPORTANT: For crate-level imports, we should also include lib.rs
167                                // as it's the crate root that defines the module structure
168                                // For now, we'll return the most specific module file we can find
169
170                                // For Rust, we need to find the module file, not the item within it
171                                // If importing my_lib::api::handle_api_request, we want to find api.rs
172                                // Split the path and try resolving progressively
173                                let parts: Vec<&str> = relative_path.split("::").collect();
174
175                                // For Rust imports, we need to resolve to the actual module file
176                                // For `crate::utils::helpers::format_output`, we want to find helpers.rs
177                                // But we also need to ensure parent modules (utils/mod.rs) are included
178
179                                // Try to find the module file that contains the imported item
180                                for i in (1..=parts.len()).rev() {
181                                    let module_path = parts[..i].join("::");
182                                    let path = ResolverUtils::module_to_path(&module_path);
183                                    let full_path = base_dir.join("src").join(path);
184
185                                    tracing::debug!(
186                                        "Trying module path '{}' at: {}",
187                                        module_path,
188                                        full_path.display()
189                                    );
190
191                                    // Try as a direct .rs file
192                                    if let Some(resolved) =
193                                        ResolverUtils::find_with_extensions(&full_path, &["rs"])
194                                    {
195                                        tracing::debug!(
196                                            "Resolved crate import to: {}",
197                                            resolved.display()
198                                        );
199                                        let validated_path =
200                                            validate_import_path(base_dir, &resolved)?;
201                                        return Ok(ResolvedPath {
202                                            path: validated_path,
203                                            is_external: false,
204                                            confidence: 0.9,
205                                        });
206                                    }
207
208                                    // Try as a directory module (mod.rs)
209                                    let mod_path = full_path.join("mod.rs");
210                                    if mod_path.exists() {
211                                        let validated_path =
212                                            validate_import_path(base_dir, &mod_path)?;
213                                        // For directory modules, we found the target
214                                        // This is the deepest module file we can find
215                                        return Ok(ResolvedPath {
216                                            path: validated_path,
217                                            is_external: false,
218                                            confidence: 0.9,
219                                        });
220                                    }
221                                }
222                            }
223                        }
224                    }
225                }
226            }
227        }
228
229        // Handle crate-relative imports
230        if module_path.starts_with("crate::") {
231            let relative_path = module_path.strip_prefix("crate::").unwrap();
232            let path = ResolverUtils::module_to_path(relative_path);
233            let full_path = base_dir.join("src").join(path);
234
235            if let Some(resolved) = ResolverUtils::find_with_extensions(&full_path, &["rs"]) {
236                let validated_path = validate_import_path(base_dir, &resolved)?;
237                return Ok(ResolvedPath {
238                    path: validated_path,
239                    is_external: false,
240                    confidence: 0.9,
241                });
242            }
243
244            // Try as a directory module (mod.rs)
245            let mod_path = full_path.join("mod.rs");
246            if mod_path.exists() {
247                let validated_path = validate_import_path(base_dir, &mod_path)?;
248                return Ok(ResolvedPath {
249                    path: validated_path,
250                    is_external: false,
251                    confidence: 0.9,
252                });
253            }
254        }
255
256        // Handle relative imports (self, super)
257        if module_path.starts_with("self::") {
258            // self:: refers to the current module
259            let rest = module_path.strip_prefix("self::").unwrap();
260            if let Some(parent) = from_file.parent() {
261                let path = ResolverUtils::module_to_path(rest);
262                let full_path = parent.join(path);
263                if let Some(resolved) = ResolverUtils::find_with_extensions(&full_path, &["rs"]) {
264                    let validated_path = validate_import_path(base_dir, &resolved)?;
265                    return Ok(ResolvedPath {
266                        path: validated_path,
267                        is_external: false,
268                        confidence: 0.9,
269                    });
270                }
271            }
272        } else if module_path.starts_with("super::") {
273            // super:: refers to the parent module
274            let rest = module_path.strip_prefix("super::").unwrap();
275            if let Some(parent) = from_file.parent() {
276                if let Some(grandparent) = parent.parent() {
277                    // For imports like "super::parent_function", we need to find the module file
278                    // that contains this function. First check if there's a lib.rs or mod.rs
279                    // in the grandparent directory
280
281                    // Try lib.rs first (common for library crates)
282                    let lib_rs = grandparent.join("lib.rs");
283                    if lib_rs.exists() {
284                        let validated_path = validate_import_path(base_dir, &lib_rs)?;
285                        return Ok(ResolvedPath {
286                            path: validated_path,
287                            is_external: false,
288                            confidence: 0.9,
289                        });
290                    }
291
292                    // Try mod.rs
293                    let mod_rs = grandparent.join("mod.rs");
294                    if mod_rs.exists() {
295                        let validated_path = validate_import_path(base_dir, &mod_rs)?;
296                        return Ok(ResolvedPath {
297                            path: validated_path,
298                            is_external: false,
299                            confidence: 0.9,
300                        });
301                    }
302
303                    // If the parent directory has a name, try parent_name.rs
304                    if let Some(parent_name) = parent.file_name() {
305                        let parent_rs =
306                            grandparent.join(format!("{}.rs", parent_name.to_string_lossy()));
307                        if parent_rs.exists() {
308                            let validated_path = validate_import_path(base_dir, &parent_rs)?;
309                            return Ok(ResolvedPath {
310                                path: validated_path,
311                                is_external: false,
312                                confidence: 0.9,
313                            });
314                        }
315                    }
316
317                    // If rest is not empty, it might be a submodule path
318                    if !rest.is_empty() {
319                        let path = ResolverUtils::module_to_path(rest);
320                        let full_path = grandparent.join(path);
321                        if let Some(resolved) =
322                            ResolverUtils::find_with_extensions(&full_path, &["rs"])
323                        {
324                            let validated_path = validate_import_path(base_dir, &resolved)?;
325                            return Ok(ResolvedPath {
326                                path: validated_path,
327                                is_external: false,
328                                confidence: 0.9,
329                            });
330                        }
331                    }
332                }
333            }
334        }
335
336        // Handle simple module names (e.g., "mod lib;" in same directory)
337        if !module_path.contains("::") {
338            if let Some(parent) = from_file.parent() {
339                // Try as a file
340                let file_path = parent.join(format!("{module_path}.rs"));
341                if file_path.exists() {
342                    let validated_path = validate_import_path(base_dir, &file_path)?;
343                    return Ok(ResolvedPath {
344                        path: validated_path,
345                        is_external: false,
346                        confidence: 0.9,
347                    });
348                }
349
350                // Try as a directory module
351                let mod_path = parent.join(module_path).join("mod.rs");
352                if mod_path.exists() {
353                    let validated_path = validate_import_path(base_dir, &mod_path)?;
354                    return Ok(ResolvedPath {
355                        path: validated_path,
356                        is_external: false,
357                        confidence: 0.9,
358                    });
359                }
360            }
361        }
362
363        // Check if it's a known external module (like stdlib)
364        if self.is_external_module(module_path) {
365            return Ok(ResolvedPath {
366                path: base_dir.join("Cargo.toml"), // Point to Cargo.toml as indicator
367                is_external: true,
368                confidence: 1.0,
369            });
370        }
371
372        // Otherwise, assume it's an external crate
373        tracing::debug!(
374            "Module '{}' not resolved locally, marking as external",
375            module_path
376        );
377        Ok(ResolvedPath {
378            path: base_dir.join("Cargo.toml"), // Point to Cargo.toml as indicator
379            is_external: true,
380            confidence: 0.5,
381        })
382    }
383
384    fn get_file_extensions(&self) -> Vec<&'static str> {
385        vec!["rs"]
386    }
387
388    fn is_external_module(&self, module_path: &str) -> bool {
389        // Common standard library crates
390        let stdlib_crates = ["std", "core", "alloc", "proc_macro", "test"];
391
392        // Get the first part of the path (before ::)
393        let first_part = module_path.split("::").next().unwrap_or(module_path);
394
395        // Check if it's a standard library crate
396        if stdlib_crates.contains(&first_part) {
397            return true;
398        }
399
400        // Simple module names (no ::) are NOT external - they're local modules
401        if !module_path.contains("::") {
402            return false;
403        }
404
405        // crate::, self::, super:: are always local
406        if module_path.starts_with("crate::")
407            || module_path.starts_with("self::")
408            || module_path.starts_with("super::")
409        {
410            return false;
411        }
412
413        // Other paths with :: might be external crates
414        // For now, we'll consider them external unless we have more context
415        true
416    }
417}