context_creator/core/semantic/languages/
rust.rs1use 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 self.correlate_types_with_imports(&mut result);
49
50 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 fn correlate_types_with_imports(&self, result: &mut AnalysisResult) {
72 use std::collections::HashMap;
73
74 let mut type_to_module: HashMap<String, String> = HashMap::new();
76
77 for import in &result.imports {
78 if import.items.is_empty() {
79 if let Some(type_name) = import.module.split("::").last() {
81 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 for item in &import.items {
89 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 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 if existing_module.ends_with(&format!("::{}", type_ref.name)) {
107 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(module_path)?;
136
137 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 if let Ok(contents) = std::fs::read_to_string(&cargo_path) {
148 for line in contents.lines() {
150 let trimmed = line.trim();
151 if trimmed.starts_with("name") && trimmed.contains('=') {
152 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 let relative_path = module_path
163 .strip_prefix(&format!("{crate_name}::"))
164 .unwrap();
165
166 let parts: Vec<&str> = relative_path.split("::").collect();
174
175 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 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 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 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 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 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 if module_path.starts_with("self::") {
258 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 let rest = module_path.strip_prefix("super::").unwrap();
275 if let Some(parent) = from_file.parent() {
276 if let Some(grandparent) = parent.parent() {
277 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 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 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_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 if !module_path.contains("::") {
338 if let Some(parent) = from_file.parent() {
339 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 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 if self.is_external_module(module_path) {
365 return Ok(ResolvedPath {
366 path: base_dir.join("Cargo.toml"), is_external: true,
368 confidence: 1.0,
369 });
370 }
371
372 tracing::debug!(
374 "Module '{}' not resolved locally, marking as external",
375 module_path
376 );
377 Ok(ResolvedPath {
378 path: base_dir.join("Cargo.toml"), 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 let stdlib_crates = ["std", "core", "alloc", "proc_macro", "test"];
391
392 let first_part = module_path.split("::").next().unwrap_or(module_path);
394
395 if stdlib_crates.contains(&first_part) {
397 return true;
398 }
399
400 if !module_path.contains("::") {
402 return false;
403 }
404
405 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 true
416 }
417}