context_creator/core/
file_expander.rs

1//! File expansion logic for semantic analysis features
2//!
3//! This module handles expanding the file list based on semantic relationships
4//! discovered during analysis (imports, type references, function calls).
5
6use crate::cli::Config;
7use crate::core::cache::FileCache;
8use crate::core::semantic::function_call_index::FunctionCallIndex;
9use crate::core::semantic::path_validator::validate_import_path;
10use crate::core::semantic::type_resolver::{ResolutionLimits, TypeResolver};
11use crate::core::walker::{perform_semantic_analysis, walk_directory, FileInfo};
12use crate::utils::error::ContextCreatorError;
13use ignore::gitignore::{Gitignore, GitignoreBuilder};
14use std::collections::{HashMap, HashSet, VecDeque};
15use std::path::{Path, PathBuf};
16use std::sync::Arc;
17
18/// Build an efficient gitignore matcher from walk options
19fn build_ignore_matcher(
20    walk_options: &crate::core::walker::WalkOptions,
21    base_path: &Path,
22) -> Option<Gitignore> {
23    if walk_options.ignore_patterns.is_empty() {
24        return None;
25    }
26
27    let mut builder = GitignoreBuilder::new(base_path);
28
29    // Add each ignore pattern
30    for pattern in &walk_options.ignore_patterns {
31        // The ignore crate handles patterns efficiently
32        let _ = builder.add_line(None, pattern);
33    }
34
35    builder.build().ok()
36}
37
38/// Detect the project root directory using git root or fallback methods
39pub fn detect_project_root(start_path: &Path) -> PathBuf {
40    // If start_path is a file, start from its parent directory
41    let start_dir = if start_path.is_file() {
42        start_path.parent().unwrap_or(start_path)
43    } else {
44        start_path
45    };
46
47    // First try to find git root
48    let mut current = start_dir;
49    loop {
50        if current.join(".git").exists() {
51            return current.to_path_buf();
52        }
53        if let Some(parent) = current.parent() {
54            current = parent;
55        } else {
56            break;
57        }
58    }
59
60    // Fallback: Look for common project markers
61    current = start_dir;
62    loop {
63        // Check for Rust project markers
64        if current.join("Cargo.toml").exists() {
65            return current.to_path_buf();
66        }
67        // Check for Node.js project markers
68        if current.join("package.json").exists() {
69            return current.to_path_buf();
70        }
71        // Check for Python project markers
72        if current.join("pyproject.toml").exists() || current.join("setup.py").exists() {
73            return current.to_path_buf();
74        }
75        // Check for generic project markers
76        if current.join("README.md").exists() || current.join("readme.md").exists() {
77            return current.to_path_buf();
78        }
79
80        if let Some(parent) = current.parent() {
81            current = parent;
82        } else {
83            break;
84        }
85    }
86
87    // Ultimate fallback: use the start directory
88    start_dir.to_path_buf()
89}
90
91/// Expand file list based on semantic relationships with full project context
92///
93/// This function takes the initial set of files and expands it to include
94/// files that define types, export functions, or are imported by the initial files.
95/// It uses the full project context to find dependencies that may be outside the initial scope.
96pub fn expand_file_list_with_context(
97    files_map: HashMap<PathBuf, FileInfo>,
98    config: &Config,
99    cache: &Arc<FileCache>,
100    walk_options: &crate::core::walker::WalkOptions,
101    all_files_context: &HashMap<PathBuf, FileInfo>,
102) -> Result<HashMap<PathBuf, FileInfo>, ContextCreatorError> {
103    expand_file_list_internal(
104        files_map,
105        config,
106        cache,
107        walk_options,
108        Some(all_files_context),
109    )
110}
111
112/// Expand file list based on semantic relationships
113///
114/// This function takes the initial set of files and expands it to include
115/// files that define types, export functions, or are imported by the initial files.
116pub fn expand_file_list(
117    files_map: HashMap<PathBuf, FileInfo>,
118    config: &Config,
119    cache: &Arc<FileCache>,
120    walk_options: &crate::core::walker::WalkOptions,
121) -> Result<HashMap<PathBuf, FileInfo>, ContextCreatorError> {
122    expand_file_list_internal(files_map, config, cache, walk_options, None)
123}
124
125/// Internal implementation of expand_file_list with optional context
126fn expand_file_list_internal(
127    files_map: HashMap<PathBuf, FileInfo>,
128    config: &Config,
129    cache: &Arc<FileCache>,
130    walk_options: &crate::core::walker::WalkOptions,
131    all_files_context: Option<&HashMap<PathBuf, FileInfo>>,
132) -> Result<HashMap<PathBuf, FileInfo>, ContextCreatorError> {
133    // If no semantic features are enabled, return as-is
134    if !config.trace_imports && !config.include_callers && !config.include_types {
135        return Ok(files_map);
136    }
137
138    let mut files_map = files_map;
139
140    // Detect the project root for secure path validation
141    let project_root = if let Some((first_path, _)) = files_map.iter().next() {
142        detect_project_root(first_path)
143    } else {
144        // If no files, use current directory
145        std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
146    };
147
148    // First, perform semantic analysis on the initial files if needed
149    if config.trace_imports || config.include_types {
150        use crate::core::semantic::analyzer::SemanticContext;
151        use crate::core::semantic::get_analyzer_for_file;
152
153        for (path, file_info) in files_map.iter_mut() {
154            // Skip if already analyzed (has imports or type references)
155            if !file_info.imports.is_empty() || !file_info.type_references.is_empty() {
156                continue;
157            }
158
159            // Read file content and analyze
160            if let Ok(content) = cache.get_or_load(path) {
161                if let Ok(Some(analyzer)) = get_analyzer_for_file(path) {
162                    let context = SemanticContext {
163                        current_file: path.clone(),
164                        base_dir: project_root.clone(),
165                        current_depth: 0,
166                        max_depth: config.semantic_depth,
167                        visited_files: HashSet::new(),
168                    };
169
170                    if let Ok(analysis) = analyzer.analyze_file(path, &content, &context) {
171                        // Convert imports to resolved file paths
172                        file_info.imports = analysis
173                            .imports
174                            .iter()
175                            .filter_map(|imp| {
176                                // Try to resolve import to file path
177                                resolve_import_to_path(&imp.module, path, &project_root)
178                            })
179                            .collect();
180                        file_info.function_calls = analysis.function_calls;
181                        file_info.type_references = analysis.type_references;
182
183                        tracing::debug!(
184                            "Initial file {} analyzed: {} imports, {} types, {} calls",
185                            path.display(),
186                            file_info.imports.len(),
187                            file_info.type_references.len(),
188                            file_info.function_calls.len()
189                        );
190                    }
191                }
192            }
193        }
194    }
195
196    // Create work queue and visited set for BFS traversal
197    let mut work_queue = VecDeque::new();
198    let mut visited_paths = HashSet::new();
199    let mut files_to_add = Vec::new();
200
201    // Initialize with files that have semantic relationships
202    for (path, file_info) in &files_map {
203        visited_paths.insert(path.clone());
204
205        // Queue files based on enabled features (depth 0 for initial files)
206        if config.include_types && !file_info.type_references.is_empty() {
207            work_queue.push_back((path.clone(), file_info.clone(), ExpansionReason::Types, 0));
208        }
209        if config.trace_imports && !file_info.imports.is_empty() {
210            tracing::debug!(
211                "Queuing {} for import expansion (has {} imports)",
212                path.display(),
213                file_info.imports.len()
214            );
215            work_queue.push_back((path.clone(), file_info.clone(), ExpansionReason::Imports, 0));
216        }
217    }
218
219    // Optimized caller expansion using pre-built index (O(n) instead of O(n²))
220    if config.include_callers {
221        let project_files = if let Some(context) = all_files_context {
222            // Use the already analyzed project files from context
223            context.values().cloned().collect()
224        } else {
225            // Fallback: walk directory and analyze files when no context is provided
226            // This is less efficient but maintains backward compatibility
227            let mut project_walk_options = walk_options.clone();
228            project_walk_options.include_patterns.clear(); // Search entire project
229
230            let mut all_project_files = walk_directory(&project_root, project_walk_options)
231                .map_err(|e| ContextCreatorError::ContextGenerationError(e.to_string()))?;
232
233            // Perform semantic analysis on project files
234            perform_semantic_analysis(&mut all_project_files, config, cache)
235                .map_err(|e| ContextCreatorError::ContextGenerationError(e.to_string()))?;
236
237            all_project_files
238        };
239
240        // Build function call index for O(1) lookups
241        let function_call_index = FunctionCallIndex::build(&project_files);
242
243        // Find all callers of functions exported by our initial files
244        let initial_files: Vec<PathBuf> = files_map.keys().cloned().collect();
245        let caller_paths = function_call_index.find_callers_of_files(&initial_files);
246
247        // Add caller files while respecting security boundaries
248        for caller_path in caller_paths {
249            if !visited_paths.contains(&caller_path) {
250                // For caller expansion, we intentionally expand beyond the original include patterns
251                // This is the purpose of the --include-callers feature
252                // However, we still respect ignore patterns for security
253                let should_ignore = walk_options.ignore_patterns.iter().any(|pattern| {
254                    glob::Pattern::new(pattern)
255                        .ok()
256                        .is_some_and(|p| p.matches_path(&caller_path))
257                });
258
259                if !should_ignore {
260                    // Find the file info from analyzed project files
261                    let caller_info = if let Some(context) = all_files_context {
262                        context.get(&caller_path).cloned()
263                    } else {
264                        // Find in project_files by path
265                        project_files
266                            .iter()
267                            .find(|f| f.path == caller_path)
268                            .cloned()
269                    };
270
271                    if let Some(caller_info) = caller_info {
272                        visited_paths.insert(caller_path.clone());
273                        files_to_add.push((caller_path, caller_info));
274                    }
275                }
276            }
277        }
278    }
279
280    // Create type resolver with circuit breakers
281    let resolution_limits = ResolutionLimits {
282        max_depth: config.semantic_depth,
283        max_visited_types: 1000, // Conservative limit
284        max_resolution_time: std::time::Duration::from_secs(30),
285    };
286    let mut type_resolver = TypeResolver::with_limits(resolution_limits);
287
288    // Process work queue
289    // Note: Cycle prevention is handled by visited_paths HashSet which tracks all processed files.
290    // This prevents infinite loops in cases like A→B→C→A by not revisiting already processed files.
291    while let Some((source_path, source_file, reason, depth)) = work_queue.pop_front() {
292        // Check if we've exceeded the semantic depth limit
293        if depth > config.semantic_depth {
294            tracing::debug!(
295                "Skipping {} (depth {} > limit {})",
296                source_path.display(),
297                depth,
298                config.semantic_depth
299            );
300            continue;
301        }
302
303        match reason {
304            ExpansionReason::Types => {
305                // Process type references
306                tracing::debug!(
307                    "Processing type references from {} (depth {})",
308                    source_path.display(),
309                    depth
310                );
311                for type_ref in &source_file.type_references {
312                    // Skip external types
313                    if type_ref.is_external {
314                        continue;
315                    }
316
317                    // Create a local copy to potentially fix the module path
318                    let mut type_ref_copy = type_ref.clone();
319
320                    // Fix module path if it contains the type name
321                    if let Some(ref module) = type_ref_copy.module {
322                        if module.ends_with(&format!("::{}", type_ref_copy.name)) {
323                            // Remove the redundant type name from the module path
324                            let corrected_module = module
325                                .strip_suffix(&format!("::{}", type_ref_copy.name))
326                                .unwrap_or(module);
327                            type_ref_copy.module = Some(corrected_module.to_string());
328                        }
329                    }
330
331                    let type_ref = &type_ref_copy;
332
333                    tracing::debug!(
334                        "  Type reference: {} (module: {:?}, definition_path: {:?}, is_external: {})",
335                        type_ref.name,
336                        type_ref.module,
337                        type_ref.definition_path,
338                        type_ref.is_external
339                    );
340
341                    // Use type resolver with circuit breakers
342                    match type_resolver.resolve_with_limits(type_ref, depth) {
343                        Err(e) => {
344                            // Circuit breaker triggered or error - skip this type
345                            if config.verbose > 0 {
346                                eprintln!("⚠️  Type resolution limited: {e}");
347                            }
348                            continue;
349                        }
350                        Ok(_) => {
351                            // Resolution succeeded, continue with normal processing
352                        }
353                    }
354
355                    // If we have a definition path, add it
356                    if let Some(ref def_path) = type_ref.definition_path {
357                        tracing::debug!("    Type has definition_path: {}", def_path.display());
358                        if !visited_paths.contains(def_path) && def_path.exists() {
359                            // Validate the path for security using the project root
360                            match validate_import_path(&project_root, def_path) {
361                                Ok(validated_path) => {
362                                    tracing::debug!(
363                                        "    Adding type definition file: {}",
364                                        validated_path.display()
365                                    );
366                                    visited_paths.insert(validated_path.clone());
367
368                                    // Create FileInfo for the definition file
369                                    let mut file_info =
370                                        create_file_info_for_path(&validated_path, &source_path)?;
371
372                                    // Perform semantic analysis on the newly found file to get its type references
373                                    if depth + 1 < config.semantic_depth {
374                                        if let Ok(content) = cache.get_or_load(&validated_path) {
375                                            use crate::core::semantic::analyzer::SemanticContext;
376                                            use crate::core::semantic::get_analyzer_for_file;
377
378                                            if let Ok(Some(analyzer)) =
379                                                get_analyzer_for_file(&validated_path)
380                                            {
381                                                let context = SemanticContext::new(
382                                                    validated_path.clone(),
383                                                    project_root.clone(),
384                                                    config.semantic_depth,
385                                                );
386
387                                                if let Ok(analysis) = analyzer.analyze_file(
388                                                    &validated_path,
389                                                    &content,
390                                                    &context,
391                                                ) {
392                                                    // Update file info with semantic data
393                                                    file_info.type_references =
394                                                        analysis.type_references;
395
396                                                    // Queue for type expansion if it has type references
397                                                    if !file_info.type_references.is_empty() {
398                                                        work_queue.push_back((
399                                                            validated_path.clone(),
400                                                            file_info.clone(),
401                                                            ExpansionReason::Types,
402                                                            depth + 1,
403                                                        ));
404                                                    }
405                                                }
406                                            }
407                                        }
408                                    }
409
410                                    files_to_add.push((validated_path.clone(), file_info));
411                                }
412                                Err(_) => {
413                                    // Path validation failed, skip this file
414                                    tracing::debug!(
415                                        "    Path validation failed for: {}",
416                                        def_path.display()
417                                    );
418                                }
419                            }
420                        }
421                    } else {
422                        tracing::debug!(
423                            "    No definition_path, attempting to find type definition file"
424                        );
425                        // Try to find the type definition file
426                        // Use the full module path
427                        let module_name = type_ref.module.as_deref();
428
429                        tracing::debug!(
430                            "    Looking for type {} with module {:?}",
431                            type_ref.name,
432                            module_name
433                        );
434                        if let Some(def_path) = find_type_definition_file(
435                            &type_ref.name,
436                            module_name,
437                            &source_path,
438                            cache,
439                        ) {
440                            tracing::debug!(
441                                "    Found type definition file: {}",
442                                def_path.display()
443                            );
444                            if !visited_paths.contains(&def_path) {
445                                // Validate the path for security using the project root
446                                match validate_import_path(&project_root, &def_path) {
447                                    Ok(validated_path) => {
448                                        tracing::debug!(
449                                            "    Adding found type definition file: {}",
450                                            validated_path.display()
451                                        );
452                                        visited_paths.insert(validated_path.clone());
453
454                                        // Create FileInfo for the definition file
455                                        let mut file_info = create_file_info_for_path(
456                                            &validated_path,
457                                            &source_path,
458                                        )?;
459
460                                        // Perform semantic analysis on the newly found file to get its type references
461                                        if depth + 1 < config.semantic_depth {
462                                            if let Ok(content) = cache.get_or_load(&validated_path)
463                                            {
464                                                use crate::core::semantic::analyzer::SemanticContext;
465                                                use crate::core::semantic::get_analyzer_for_file;
466
467                                                if let Ok(Some(analyzer)) =
468                                                    get_analyzer_for_file(&validated_path)
469                                                {
470                                                    let context = SemanticContext::new(
471                                                        validated_path.clone(),
472                                                        project_root.clone(),
473                                                        config.semantic_depth,
474                                                    );
475
476                                                    if let Ok(analysis) = analyzer.analyze_file(
477                                                        &validated_path,
478                                                        &content,
479                                                        &context,
480                                                    ) {
481                                                        // Update file info with semantic data
482                                                        file_info.type_references =
483                                                            analysis.type_references;
484
485                                                        // Queue for type expansion if it has type references
486                                                        if !file_info.type_references.is_empty() {
487                                                            work_queue.push_back((
488                                                                validated_path.clone(),
489                                                                file_info.clone(),
490                                                                ExpansionReason::Types,
491                                                                depth + 1,
492                                                            ));
493                                                        }
494                                                    }
495                                                }
496                                            }
497                                        }
498
499                                        files_to_add.push((validated_path, file_info));
500                                    }
501                                    Err(_) => {
502                                        // Path validation failed, skip this file
503                                        tracing::debug!(
504                                            "    Path validation failed for found file: {}",
505                                            def_path.display()
506                                        );
507                                    }
508                                }
509                            }
510                        } else {
511                            tracing::debug!(
512                                "    Could not find type definition file for: {}",
513                                type_ref.name
514                            );
515                        }
516                    }
517                }
518            }
519            ExpansionReason::Imports => {
520                // Process each import in the source file
521                for import_path in &source_file.imports {
522                    // Skip if doesn't exist
523                    if !import_path.exists() {
524                        continue;
525                    }
526
527                    // Check if already visited (need to check both original and canonical paths)
528                    let canonical_import = import_path
529                        .canonicalize()
530                        .unwrap_or_else(|_| import_path.clone());
531                    if visited_paths.contains(import_path)
532                        || visited_paths.contains(&canonical_import)
533                    {
534                        continue;
535                    }
536
537                    // Validate the import path for security
538                    match validate_import_path(&project_root, import_path) {
539                        Ok(validated_path) => {
540                            visited_paths.insert(validated_path.clone());
541
542                            // For Rust files, if we're importing a module, also include lib.rs
543                            if source_path.extension() == Some(std::ffi::OsStr::new("rs")) {
544                                // Check if this is a module file (not main.rs or lib.rs itself)
545                                if let Some(parent) = validated_path.parent() {
546                                    let lib_rs = parent.join("lib.rs");
547                                    if lib_rs.exists()
548                                        && lib_rs != validated_path
549                                        && !visited_paths.contains(&lib_rs)
550                                    {
551                                        // Check if lib.rs declares this module
552                                        if let Ok(lib_content) = cache.get_or_load(&lib_rs) {
553                                            let module_name = validated_path
554                                                .file_stem()
555                                                .and_then(|s| s.to_str())
556                                                .unwrap_or("");
557                                            if lib_content.contains(&format!("mod {module_name};"))
558                                                || lib_content
559                                                    .contains(&format!("pub mod {module_name};"))
560                                            {
561                                                // Add lib.rs to files to include
562                                                visited_paths.insert(lib_rs.clone());
563                                                if let Some(context) = all_files_context {
564                                                    if let Some(lib_file) = context.get(&lib_rs) {
565                                                        files_to_add.push((
566                                                            lib_rs.clone(),
567                                                            lib_file.clone(),
568                                                        ));
569                                                    }
570                                                } else {
571                                                    let lib_info = create_file_info_for_path(
572                                                        &lib_rs,
573                                                        &source_path,
574                                                    )?;
575                                                    files_to_add.push((lib_rs, lib_info));
576                                                }
577                                            }
578                                        }
579                                    }
580                                }
581                            }
582
583                            // Check if we have this file in the context first
584                            let mut file_info = if let Some(context) = all_files_context {
585                                // Try to canonicalize for lookup, but fall back to validated_path
586                                let lookup_path = validated_path
587                                    .canonicalize()
588                                    .unwrap_or_else(|_| validated_path.clone());
589
590                                // Also try the non-canonical path
591                                let context_file = context
592                                    .get(&lookup_path)
593                                    .or_else(|| context.get(&validated_path));
594
595                                if let Some(context_file) = context_file {
596                                    // Use the pre-analyzed file from context
597                                    let mut file = context_file.clone();
598                                    // Mark that this file was imported by the source file
599                                    file.imported_by.push(source_path.clone());
600                                    file
601                                } else {
602                                    // Create FileInfo for the imported file
603                                    let mut file =
604                                        create_file_info_for_path(&validated_path, &source_path)?;
605                                    file.imported_by.push(source_path.clone());
606                                    file
607                                }
608                            } else {
609                                // No context, create from scratch
610                                let mut file =
611                                    create_file_info_for_path(&validated_path, &source_path)?;
612                                file.imported_by.push(source_path.clone());
613                                file
614                            };
615
616                            // Queue for next depth level if within limits
617                            if depth + 1 < config.semantic_depth {
618                                // If we already have semantic data from context, use it
619                                if !file_info.imports.is_empty()
620                                    || !file_info.type_references.is_empty()
621                                    || !file_info.function_calls.is_empty()
622                                {
623                                    // Already analyzed, just queue if needed
624                                    if !file_info.imports.is_empty() {
625                                        work_queue.push_back((
626                                            validated_path.clone(),
627                                            file_info.clone(),
628                                            ExpansionReason::Imports,
629                                            depth + 1,
630                                        ));
631                                    }
632                                    if config.include_types && !file_info.type_references.is_empty()
633                                    {
634                                        work_queue.push_back((
635                                            validated_path.clone(),
636                                            file_info.clone(),
637                                            ExpansionReason::Types,
638                                            depth + 1,
639                                        ));
640                                    }
641                                } else if let Ok(content) = cache.get_or_load(&validated_path) {
642                                    // Perform semantic analysis on the imported file
643                                    use crate::core::semantic::analyzer::SemanticContext;
644                                    use crate::core::semantic::get_analyzer_for_file;
645
646                                    if let Ok(Some(analyzer)) =
647                                        get_analyzer_for_file(&validated_path)
648                                    {
649                                        let context = SemanticContext::new(
650                                            validated_path.clone(),
651                                            project_root.clone(),
652                                            config.semantic_depth,
653                                        );
654
655                                        if let Ok(analysis) = analyzer.analyze_file(
656                                            &validated_path,
657                                            &content,
658                                            &context,
659                                        ) {
660                                            // Update file info with semantic data
661                                            file_info.imports = analysis
662                                                .imports
663                                                .iter()
664                                                .filter_map(|imp| {
665                                                    // Try to resolve import to file path
666                                                    resolve_import_to_path(
667                                                        &imp.module,
668                                                        &validated_path,
669                                                        &project_root,
670                                                    )
671                                                })
672                                                .collect();
673                                            file_info.function_calls = analysis.function_calls;
674                                            file_info.type_references = analysis.type_references;
675
676                                            // Queue if it has imports
677                                            if !file_info.imports.is_empty() {
678                                                work_queue.push_back((
679                                                    validated_path.clone(),
680                                                    file_info.clone(),
681                                                    ExpansionReason::Imports,
682                                                    depth + 1,
683                                                ));
684                                            }
685                                        }
686                                    }
687                                }
688                            }
689
690                            files_to_add.push((validated_path, file_info));
691                        }
692                        Err(_) => {
693                            // Path validation failed, skip this import
694                            if config.verbose > 0 {
695                                eprintln!(
696                                    "⚠️  Skipping invalid import path: {}",
697                                    import_path.display()
698                                );
699                            }
700                        }
701                    }
702                }
703            }
704        }
705    }
706
707    // Add new files to the map
708    for (path, file_info) in files_to_add {
709        files_map.insert(path, file_info);
710    }
711
712    // Update imported_by relationships for proper prioritization
713    update_import_relationships(&mut files_map);
714
715    // Build ignore matcher for efficient filtering
716    if let Some(ignore_matcher) = build_ignore_matcher(walk_options, &project_root) {
717        // Remove ignored files from the final output
718        // This is extremely efficient as the ignore crate uses optimized algorithms
719        let ignored_files: Vec<PathBuf> = files_map
720            .keys()
721            .filter(|path| {
722                // The ignore crate's Match type indicates if a path should be ignored
723                ignore_matcher.matched(path, path.is_dir()).is_ignore()
724            })
725            .cloned()
726            .collect();
727
728        for ignored_path in ignored_files {
729            files_map.remove(&ignored_path);
730        }
731    }
732
733    Ok(files_map)
734}
735
736/// Reason for expanding to include a file
737#[derive(Debug, Clone, Copy)]
738enum ExpansionReason {
739    Types,
740    Imports,
741}
742
743/// Create a basic FileInfo for a newly discovered file
744fn create_file_info_for_path(
745    path: &PathBuf,
746    source_path: &Path,
747) -> Result<FileInfo, ContextCreatorError> {
748    use crate::utils::file_ext::FileType;
749    use std::fs;
750
751    let metadata = fs::metadata(path)?;
752    let file_type = FileType::from_path(path);
753
754    // Calculate relative path from common ancestor
755    let relative_path = path
756        .strip_prefix(common_ancestor(path, source_path))
757        .unwrap_or(path)
758        .to_path_buf();
759
760    Ok(FileInfo {
761        path: path.clone(),
762        relative_path,
763        size: metadata.len(),
764        file_type,
765        priority: 1.0, // Default priority, will be adjusted by prioritizer
766        imports: Vec::new(),
767        imported_by: vec![source_path.to_path_buf()], // Track who caused this file to be included
768        function_calls: Vec::new(),
769        type_references: Vec::new(),
770        exported_functions: Vec::new(),
771    })
772}
773
774/// Find the lowest common ancestor (LCA) of two paths using a proper set-based approach
775fn common_ancestor(path1: &Path, path2: &Path) -> PathBuf {
776    use std::collections::HashSet;
777
778    // Collect all ancestors of path1 into a set for efficient lookup
779    let ancestors1: HashSet<&Path> = path1.ancestors().collect();
780
781    // Find the first ancestor of path2 that is also in ancestors1
782    // This will be the lowest common ancestor since ancestors() returns in order from leaf to root
783    for ancestor in path2.ancestors() {
784        if ancestors1.contains(ancestor) {
785            return ancestor.to_path_buf();
786        }
787    }
788
789    // If no common ancestor found (shouldn't happen in normal filesystem),
790    // fallback to appropriate root for the platform
791    #[cfg(windows)]
792    {
793        // On Windows, try to get the root from one of the paths
794        if let Some(root) = path1.ancestors().last() {
795            root.to_path_buf()
796        } else {
797            PathBuf::from("C:\\")
798        }
799    }
800    #[cfg(not(windows))]
801    {
802        PathBuf::from("/")
803    }
804}
805
806/// Check if a file contains a definition for a given type name using AST parsing.
807fn file_contains_definition(path: &Path, content: &str, type_name: &str) -> bool {
808    // Determine the language from the file extension.
809    let language = match path.extension().and_then(|s| s.to_str()) {
810        Some("rs") => Some(tree_sitter_rust::language()),
811        Some("py") => Some(tree_sitter_python::language()),
812        Some("ts") | Some("tsx") => Some(tree_sitter_typescript::language_typescript()),
813        Some("js") | Some("jsx") => Some(tree_sitter_javascript::language()),
814        _ => None,
815    };
816
817    if let Some(language) = language {
818        let mut parser = tree_sitter::Parser::new();
819        if parser.set_language(language).is_err() {
820            return false;
821        }
822
823        if let Some(tree) = parser.parse(content, None) {
824            // Language-specific queries for type definitions (without predicates)
825            let query_text = match path.extension().and_then(|s| s.to_str()) {
826                Some("rs") => {
827                    r#"
828                    [
829                      (struct_item name: (type_identifier) @name)
830                      (enum_item name: (type_identifier) @name)
831                      (trait_item name: (type_identifier) @name)
832                      (type_item name: (type_identifier) @name)
833                      (union_item name: (type_identifier) @name)
834                    ]
835                "#
836                }
837                Some("py") => {
838                    r#"
839                    [
840                      (class_definition name: (identifier) @name)
841                      (function_definition name: (identifier) @name)
842                    ]
843                "#
844                }
845                Some("ts") | Some("tsx") => {
846                    r#"
847                    [
848                      (interface_declaration name: (type_identifier) @name)
849                      (type_alias_declaration name: (type_identifier) @name)
850                      (class_declaration name: (type_identifier) @name)
851                      (enum_declaration name: (identifier) @name)
852                    ]
853                "#
854                }
855                Some("js") | Some("jsx") => {
856                    r#"
857                    [
858                      (class_declaration name: (identifier) @name)
859                      (function_declaration name: (identifier) @name)
860                    ]
861                "#
862                }
863                _ => return false,
864            };
865
866            if let Ok(query) = tree_sitter::Query::new(language, query_text) {
867                let mut cursor = tree_sitter::QueryCursor::new();
868                let matches = cursor.matches(&query, tree.root_node(), content.as_bytes());
869
870                // Check each match to see if the captured name matches our target type
871                for m in matches {
872                    for capture in m.captures {
873                        if let Ok(captured_text) = capture.node.utf8_text(content.as_bytes()) {
874                            if captured_text == type_name {
875                                return true;
876                            }
877                        }
878                    }
879                }
880                return false;
881            }
882        }
883    }
884    false
885}
886
887/// Find a type definition file by searching nearby paths
888fn find_type_definition_file(
889    type_name: &str,
890    module_name: Option<&str>,
891    source_file: &Path,
892    cache: &FileCache,
893) -> Option<PathBuf> {
894    tracing::debug!(
895        "find_type_definition_file: type_name={}, module_name={:?}, source_file={}",
896        type_name,
897        module_name,
898        source_file.display()
899    );
900    // Get the directory of the source file
901    let source_dir = source_file.parent()?;
902
903    // Also get the project root (go up to find src directory)
904    let mut project_root = source_dir;
905    while let Some(parent) = project_root.parent() {
906        // If we find a Cargo.toml or src directory, the parent is likely the project root
907        if parent.join("Cargo.toml").exists() || parent.join("src").exists() {
908            project_root = parent;
909            break;
910        }
911        // If current dir is named "src", its parent is likely the project root
912        if project_root.file_name() == Some(std::ffi::OsStr::new("src")) {
913            project_root = parent;
914            break;
915        }
916        project_root = parent;
917    }
918
919    // Convert type name to lowercase for file matching
920    let type_name_lower = type_name.to_lowercase();
921
922    // Common patterns for type definition files
923    let mut patterns = vec![
924        // Direct file name matches
925        format!("{type_name_lower}.rs"),
926        format!("{type_name_lower}.py"),
927        format!("{type_name_lower}.ts"),
928        format!("{type_name_lower}.js"),
929        format!("{type_name_lower}.tsx"),
930        format!("{type_name_lower}.jsx"),
931        // Types files
932        "types.rs".to_string(),
933        "types.py".to_string(),
934        "types.ts".to_string(),
935        "types.js".to_string(),
936        // Module files
937        "mod.rs".to_string(),
938        "index.ts".to_string(),
939        "index.js".to_string(),
940        "__init__.py".to_string(),
941        // Common type definition patterns
942        format!("{type_name_lower}_types.rs"),
943        format!("{type_name_lower}_type.rs"),
944        format!("{type_name_lower}s.rs"), // plural form
945    ];
946
947    // If we have a module name, add module-based patterns
948    if let Some(module) = module_name {
949        // Handle Rust module paths like "crate::models"
950        if module.starts_with("crate::") {
951            let relative_path = module.strip_prefix("crate::").unwrap();
952            // Convert module path to file path (e.g., "models" or "domain::types")
953            let module_path = relative_path.replace("::", "/");
954
955            // For crate:: paths, we need to look in the src directory
956            patterns.insert(0, format!("src/{module_path}.rs"));
957            patterns.insert(1, format!("src/{module_path}/mod.rs"));
958            patterns.insert(2, format!("{module_path}.rs"));
959            patterns.insert(3, format!("{module_path}/mod.rs"));
960        } else if module.contains("::") {
961            // Handle other module paths with ::
962            let module_path = module.replace("::", "/");
963            patterns.insert(0, format!("{module_path}.rs"));
964            patterns.insert(1, format!("{module_path}/mod.rs"));
965        } else {
966            // Simple module names
967            let module_lower = module.to_lowercase();
968            patterns.insert(0, format!("{module_lower}.rs"));
969            patterns.insert(1, format!("{module_lower}.py"));
970            patterns.insert(2, format!("{module_lower}.ts"));
971            patterns.insert(3, format!("{module_lower}.js"));
972            patterns.insert(4, format!("{module_lower}.tsx"));
973            patterns.insert(5, format!("{module_lower}.jsx"));
974            patterns.insert(6, format!("{module}.rs")); // Also try original case
975            patterns.insert(7, format!("{module}.py"));
976            patterns.insert(8, format!("{module}.ts"));
977            patterns.insert(9, format!("{module}.js"));
978        }
979    }
980
981    // Search in current directory first
982    for pattern in &patterns {
983        let candidate = source_dir.join(pattern);
984        if candidate.exists() {
985            // Read the file to verify it contains the type definition
986            if let Ok(content) = cache.get_or_load(&candidate) {
987                // Use AST-based validation to check for type definitions
988                if file_contains_definition(&candidate, &content, type_name) {
989                    return Some(candidate);
990                }
991            }
992        }
993    }
994
995    // Search in parent directory
996    if let Some(parent_dir) = source_dir.parent() {
997        for pattern in &patterns {
998            let candidate = parent_dir.join(pattern);
999            if candidate.exists() {
1000                if let Ok(content) = cache.get_or_load(&candidate) {
1001                    if file_contains_definition(&candidate, &content, type_name) {
1002                        return Some(candidate);
1003                    }
1004                }
1005            }
1006        }
1007    }
1008
1009    // Search in common module directories relative to project root
1010    let search_dirs = vec![
1011        project_root.to_path_buf(),
1012        project_root.join("src"),
1013        project_root.join("src/models"),
1014        project_root.join("src/types"),
1015        project_root.join("shared"),
1016        project_root.join("shared/types"),
1017        project_root.join("lib"),
1018        project_root.join("domain"),
1019        source_dir.join("models"),
1020        source_dir.join("types"),
1021    ];
1022
1023    for search_dir in search_dirs {
1024        if search_dir.exists() {
1025            for pattern in &patterns {
1026                let candidate = search_dir.join(pattern);
1027                if candidate.exists() {
1028                    if let Ok(content) = cache.get_or_load(&candidate) {
1029                        if file_contains_definition(&candidate, &content, type_name) {
1030                            return Some(candidate);
1031                        }
1032                    }
1033                }
1034            }
1035        }
1036    }
1037
1038    None
1039}
1040
1041/// Resolve an import module name to a file path
1042fn resolve_import_to_path(
1043    module_name: &str,
1044    importing_file: &Path,
1045    project_root: &Path,
1046) -> Option<PathBuf> {
1047    // Use the semantic module resolver system
1048    use crate::core::semantic::get_module_resolver_for_file;
1049
1050    // Get the appropriate resolver for this file type
1051    let resolver = match get_module_resolver_for_file(importing_file) {
1052        Ok(Some(r)) => r,
1053        _ => {
1054            // No resolver available, fall back to simple resolution
1055            let source_dir = importing_file.parent()?;
1056
1057            // Handle relative imports (Python style: ".", "..", "..sibling")
1058            if module_name.starts_with('.') {
1059                return resolve_relative_import(module_name, source_dir, project_root);
1060            }
1061
1062            // Language-specific resolution based on file extension
1063            return match importing_file.extension().and_then(|s| s.to_str()) {
1064                Some("rs") => resolve_rust_import(module_name, source_dir, project_root),
1065                Some("py") => resolve_python_import(module_name, source_dir, project_root),
1066                Some("js") | Some("jsx") => {
1067                    resolve_javascript_import(module_name, source_dir, project_root)
1068                }
1069                Some("ts") | Some("tsx") => {
1070                    resolve_typescript_import(module_name, source_dir, project_root)
1071                }
1072                Some("go") => resolve_go_import(module_name, source_dir, project_root),
1073                _ => None,
1074            };
1075        }
1076    };
1077
1078    // Resolve the import
1079    match resolver.resolve_import(module_name, importing_file, project_root) {
1080        Ok(resolved) => {
1081            if resolved.is_external {
1082                // Skip external modules
1083                None
1084            } else {
1085                Some(resolved.path)
1086            }
1087        }
1088        Err(_) => {
1089            // Fallback to simple resolution for backwards compatibility
1090            let source_dir = importing_file.parent()?;
1091
1092            // Handle relative imports (Python style: ".", "..", "..sibling")
1093            if module_name.starts_with('.') {
1094                return resolve_relative_import(module_name, source_dir, project_root);
1095            }
1096
1097            // Language-specific resolution based on file extension
1098            match importing_file.extension().and_then(|s| s.to_str()) {
1099                Some("rs") => resolve_rust_import(module_name, source_dir, project_root),
1100                Some("py") => resolve_python_import(module_name, source_dir, project_root),
1101                Some("js") | Some("jsx") => {
1102                    resolve_javascript_import(module_name, source_dir, project_root)
1103                }
1104                Some("ts") | Some("tsx") => {
1105                    resolve_typescript_import(module_name, source_dir, project_root)
1106                }
1107                Some("go") => resolve_go_import(module_name, source_dir, project_root),
1108                _ => None,
1109            }
1110        }
1111    }
1112}
1113
1114/// Resolve relative imports (e.g., "..", ".", "../sibling")
1115fn resolve_relative_import(
1116    module_name: &str,
1117    source_dir: &Path,
1118    _project_root: &Path,
1119) -> Option<PathBuf> {
1120    let mut path = source_dir.to_path_buf();
1121
1122    // Count leading dots
1123    let dots: Vec<&str> = module_name.split('/').collect();
1124    if dots.is_empty() {
1125        return None;
1126    }
1127
1128    // Handle ".." for parent directory
1129    for part in &dots {
1130        if *part == ".." {
1131            path = path.parent()?.to_path_buf();
1132        } else if *part == "." {
1133            // Stay in current directory
1134        } else {
1135            // This is the actual module name after dots
1136            path = path.join(part);
1137            break;
1138        }
1139    }
1140
1141    // Try common file extensions
1142    for ext in &["py", "js", "ts", "rs"] {
1143        let file_path = path.with_extension(ext);
1144        if file_path.exists() {
1145            return Some(file_path);
1146        }
1147    }
1148
1149    // Try as directory with index/mod/__init__ files
1150    if path.is_dir() {
1151        for index_file in &["__init__.py", "index.js", "index.ts", "mod.rs"] {
1152            let index_path = path.join(index_file);
1153            if index_path.exists() {
1154                return Some(index_path);
1155            }
1156        }
1157    }
1158
1159    None
1160}
1161
1162/// Resolve Rust module imports
1163fn resolve_rust_import(
1164    module_name: &str,
1165    source_dir: &Path,
1166    project_root: &Path,
1167) -> Option<PathBuf> {
1168    // Handle crate:: prefix
1169    let module_path = if module_name.starts_with("crate::") {
1170        module_name.strip_prefix("crate::").unwrap()
1171    } else {
1172        module_name
1173    };
1174
1175    // Convert module path to file path (e.g., "foo::bar" -> "foo/bar")
1176    let parts: Vec<&str> = module_path.split("::").collect();
1177
1178    // Try in source directory first
1179    let mut path = source_dir.to_path_buf();
1180    for part in &parts {
1181        path = path.join(part);
1182    }
1183
1184    // Try as .rs file
1185    let rs_file = path.with_extension("rs");
1186    if rs_file.exists() {
1187        return Some(rs_file);
1188    }
1189
1190    // Try as mod.rs in directory
1191    let mod_file = path.join("mod.rs");
1192    if mod_file.exists() {
1193        return Some(mod_file);
1194    }
1195
1196    // Try from project root src directory
1197    let src_path = project_root.join("src");
1198    if src_path.exists() {
1199        let mut path = src_path;
1200        for part in &parts {
1201            path = path.join(part);
1202        }
1203
1204        let rs_file = path.with_extension("rs");
1205        if rs_file.exists() {
1206            return Some(rs_file);
1207        }
1208
1209        let mod_file = path.join("mod.rs");
1210        if mod_file.exists() {
1211            return Some(mod_file);
1212        }
1213    }
1214
1215    None
1216}
1217
1218/// Resolve Python module imports
1219fn resolve_python_import(
1220    module_name: &str,
1221    source_dir: &Path,
1222    project_root: &Path,
1223) -> Option<PathBuf> {
1224    // Convert module path to file path (e.g., "foo.bar" -> "foo/bar")
1225    let parts: Vec<&str> = module_name.split('.').collect();
1226
1227    // Try from source directory
1228    let mut path = source_dir.to_path_buf();
1229    for part in &parts {
1230        path = path.join(part);
1231    }
1232
1233    // Try as .py file
1234    let py_file = path.with_extension("py");
1235    if py_file.exists() {
1236        return Some(py_file);
1237    }
1238
1239    // Try as __init__.py in directory
1240    let init_file = path.join("__init__.py");
1241    if init_file.exists() {
1242        return Some(init_file);
1243    }
1244
1245    // Try from project root
1246    let mut path = project_root.to_path_buf();
1247    for part in &parts {
1248        path = path.join(part);
1249    }
1250
1251    let py_file = path.with_extension("py");
1252    if py_file.exists() {
1253        return Some(py_file);
1254    }
1255
1256    let init_file = path.join("__init__.py");
1257    if init_file.exists() {
1258        return Some(init_file);
1259    }
1260
1261    None
1262}
1263
1264/// Resolve JavaScript module imports
1265fn resolve_javascript_import(
1266    module_name: &str,
1267    source_dir: &Path,
1268    _project_root: &Path,
1269) -> Option<PathBuf> {
1270    // Handle relative paths
1271    if module_name.starts_with("./") || module_name.starts_with("../") {
1272        let path = source_dir.join(module_name);
1273
1274        // Try exact path first
1275        if path.exists() {
1276            return Some(path);
1277        }
1278
1279        // Try with .js extension
1280        let js_file = path.with_extension("js");
1281        if js_file.exists() {
1282            return Some(js_file);
1283        }
1284
1285        // Try with .jsx extension
1286        let jsx_file = path.with_extension("jsx");
1287        if jsx_file.exists() {
1288            return Some(jsx_file);
1289        }
1290
1291        // Try as directory with index.js
1292        let index_file = path.join("index.js");
1293        if index_file.exists() {
1294            return Some(index_file);
1295        }
1296    }
1297
1298    // For non-relative imports, they're likely npm modules - skip
1299    None
1300}
1301
1302/// Resolve TypeScript module imports
1303fn resolve_typescript_import(
1304    module_name: &str,
1305    source_dir: &Path,
1306    _project_root: &Path,
1307) -> Option<PathBuf> {
1308    // Handle relative paths
1309    if module_name.starts_with("./") || module_name.starts_with("../") {
1310        let path = source_dir.join(module_name);
1311
1312        // Try exact path first
1313        if path.exists() {
1314            return Some(path);
1315        }
1316
1317        // Try with .ts extension
1318        let ts_file = path.with_extension("ts");
1319        if ts_file.exists() {
1320            return Some(ts_file);
1321        }
1322
1323        // Try with .tsx extension
1324        let tsx_file = path.with_extension("tsx");
1325        if tsx_file.exists() {
1326            return Some(tsx_file);
1327        }
1328
1329        // Try as directory with index.ts
1330        let index_file = path.join("index.ts");
1331        if index_file.exists() {
1332            return Some(index_file);
1333        }
1334    }
1335
1336    // For non-relative imports, they're likely npm modules - skip
1337    None
1338}
1339
1340/// Resolve Go module imports
1341fn resolve_go_import(
1342    module_name: &str,
1343    _source_dir: &Path,
1344    project_root: &Path,
1345) -> Option<PathBuf> {
1346    // Go imports are typically package-based
1347    // Skip external packages (those with dots in the first part usually)
1348    if module_name.contains('/') && module_name.split('/').next()?.contains('.') {
1349        return None; // External package
1350    }
1351
1352    // Try to find in project
1353    let parts: Vec<&str> = module_name.split('/').collect();
1354    let mut path = project_root.to_path_buf();
1355
1356    for part in parts {
1357        path = path.join(part);
1358    }
1359
1360    // Go files in a directory form a package
1361    if path.is_dir() {
1362        // Return the first .go file in the directory (excluding tests)
1363        if let Ok(entries) = std::fs::read_dir(&path) {
1364            for entry in entries.flatten() {
1365                let file_path = entry.path();
1366                if file_path.extension() == Some(std::ffi::OsStr::new("go")) {
1367                    let file_name = file_path.file_name()?.to_string_lossy();
1368                    if !file_name.ends_with("_test.go") {
1369                        return Some(file_path);
1370                    }
1371                }
1372            }
1373        }
1374    }
1375
1376    None
1377}
1378
1379/// Update import relationships after expansion
1380fn update_import_relationships(files_map: &mut HashMap<PathBuf, FileInfo>) {
1381    // Build a map of which files import which
1382    let mut import_map: HashMap<PathBuf, Vec<PathBuf>> = HashMap::new();
1383
1384    for (path, file_info) in files_map.iter() {
1385        for import_path in &file_info.imports {
1386            // Track which files import which
1387            import_map
1388                .entry(import_path.clone())
1389                .or_default()
1390                .push(path.clone());
1391        }
1392    }
1393
1394    // Update imported_by fields
1395    for (imported_path, importers) in import_map {
1396        if let Some(file_info) = files_map.get_mut(&imported_path) {
1397            file_info.imported_by.extend(importers);
1398            file_info.imported_by.sort();
1399            file_info.imported_by.dedup();
1400        }
1401    }
1402}
1403
1404#[cfg(test)]
1405mod tests {
1406    use super::*;
1407    use crate::utils::file_ext::FileType;
1408
1409    #[test]
1410    fn test_no_expansion_when_disabled() {
1411        let mut files_map = HashMap::new();
1412        files_map.insert(
1413            PathBuf::from("test.rs"),
1414            FileInfo {
1415                path: PathBuf::from("test.rs"),
1416                relative_path: PathBuf::from("test.rs"),
1417                size: 100,
1418                file_type: FileType::Rust,
1419                priority: 1.0,
1420                imports: Vec::new(),
1421                imported_by: Vec::new(),
1422                function_calls: Vec::new(),
1423                type_references: Vec::new(),
1424                exported_functions: Vec::new(),
1425            },
1426        );
1427
1428        let config = Config {
1429            trace_imports: false,
1430            include_callers: false,
1431            include_types: false,
1432            ..Default::default()
1433        };
1434
1435        let cache = Arc::new(FileCache::new());
1436        let walk_options = crate::core::walker::WalkOptions {
1437            max_file_size: None,
1438            follow_links: false,
1439            include_hidden: false,
1440            parallel: false,
1441            ignore_file: ".context-creator-ignore".to_string(),
1442            ignore_patterns: vec![],
1443            include_patterns: vec![],
1444            custom_priorities: vec![],
1445            filter_binary_files: false,
1446        };
1447        let result = expand_file_list(files_map.clone(), &config, &cache, &walk_options).unwrap();
1448
1449        assert_eq!(result.len(), 1);
1450    }
1451
1452    #[test]
1453    fn test_common_ancestor() {
1454        #[cfg(windows)]
1455        {
1456            let path1 = PathBuf::from("C:\\Users\\user\\project\\src\\main.rs");
1457            let path2 = PathBuf::from("C:\\Users\\user\\project\\lib\\util.rs");
1458            let ancestor = common_ancestor(&path1, &path2);
1459            assert_eq!(ancestor, PathBuf::from("C:\\Users\\user\\project"));
1460
1461            // Test with same directory
1462            let path3 = PathBuf::from("C:\\Users\\user\\project\\main.rs");
1463            let path4 = PathBuf::from("C:\\Users\\user\\project\\util.rs");
1464            let ancestor2 = common_ancestor(&path3, &path4);
1465            assert_eq!(ancestor2, PathBuf::from("C:\\Users\\user\\project"));
1466
1467            // Test with nested paths
1468            let path5 = PathBuf::from("C:\\Users\\user\\project\\src\\deep\\nested\\file.rs");
1469            let path6 = PathBuf::from("C:\\Users\\user\\project\\src\\main.rs");
1470            let ancestor3 = common_ancestor(&path5, &path6);
1471            assert_eq!(ancestor3, PathBuf::from("C:\\Users\\user\\project\\src"));
1472
1473            // Test with completely different drives
1474            let path7 = PathBuf::from("C:\\Program Files\\tool");
1475            let path8 = PathBuf::from("D:\\Users\\user\\file");
1476            let ancestor4 = common_ancestor(&path7, &path8);
1477            // Should fall back to C:\ (root from first path)
1478            assert_eq!(ancestor4, PathBuf::from("C:\\"));
1479        }
1480
1481        #[cfg(not(windows))]
1482        {
1483            let path1 = PathBuf::from("/home/user/project/src/main.rs");
1484            let path2 = PathBuf::from("/home/user/project/lib/util.rs");
1485            let ancestor = common_ancestor(&path1, &path2);
1486            assert_eq!(ancestor, PathBuf::from("/home/user/project"));
1487
1488            // Test with same directory
1489            let path3 = PathBuf::from("/home/user/project/main.rs");
1490            let path4 = PathBuf::from("/home/user/project/util.rs");
1491            let ancestor2 = common_ancestor(&path3, &path4);
1492            assert_eq!(ancestor2, PathBuf::from("/home/user/project"));
1493
1494            // Test with nested paths
1495            let path5 = PathBuf::from("/home/user/project/src/deep/nested/file.rs");
1496            let path6 = PathBuf::from("/home/user/project/src/main.rs");
1497            let ancestor3 = common_ancestor(&path5, &path6);
1498            assert_eq!(ancestor3, PathBuf::from("/home/user/project/src"));
1499
1500            // Test with completely different top-level directories
1501            let path7 = PathBuf::from("/usr/local/bin/tool");
1502            let path8 = PathBuf::from("/home/user/file");
1503            let ancestor4 = common_ancestor(&path7, &path8);
1504            assert_eq!(ancestor4, PathBuf::from("/"));
1505
1506            // Test with one path being an ancestor of the other
1507            let path9 = PathBuf::from("/home/user/project");
1508            let path10 = PathBuf::from("/home/user/project/src/main.rs");
1509            let ancestor5 = common_ancestor(&path9, &path10);
1510            assert_eq!(ancestor5, PathBuf::from("/home/user/project"));
1511        }
1512    }
1513
1514    #[test]
1515    fn test_file_contains_definition() {
1516        // Debug: Test simple tree-sitter parsing first
1517        let rust_content = r#"
1518            pub struct MyStruct {
1519                field1: String,
1520                field2: i32,
1521            }
1522            
1523            pub enum MyEnum {
1524                Variant1,
1525                Variant2(String),
1526            }
1527            
1528            pub trait MyTrait {
1529                fn method(&self);
1530            }
1531        "#;
1532
1533        let mut parser = tree_sitter::Parser::new();
1534        parser.set_language(tree_sitter_rust::language()).unwrap();
1535        let tree = parser.parse(rust_content, None).unwrap();
1536
1537        // Test simple query without predicate first
1538        let simple_query = r#"
1539            [
1540              (struct_item name: (type_identifier) @name)
1541              (enum_item name: (type_identifier) @name)
1542              (trait_item name: (type_identifier) @name)
1543            ]
1544        "#;
1545
1546        let query = tree_sitter::Query::new(tree_sitter_rust::language(), simple_query).unwrap();
1547        let mut cursor = tree_sitter::QueryCursor::new();
1548        let matches: Vec<_> = cursor
1549            .matches(&query, tree.root_node(), rust_content.as_bytes())
1550            .collect();
1551
1552        // Should find 3 definitions: MyStruct, MyEnum, MyTrait
1553        assert_eq!(matches.len(), 3, "Should find exactly 3 type definitions");
1554
1555        // Test the actual function with a simpler approach
1556        let rust_path = PathBuf::from("test.rs");
1557        assert!(file_contains_definition(
1558            &rust_path,
1559            rust_content,
1560            "MyStruct"
1561        ));
1562        assert!(file_contains_definition(&rust_path, rust_content, "MyEnum"));
1563        assert!(file_contains_definition(
1564            &rust_path,
1565            rust_content,
1566            "MyTrait"
1567        ));
1568        assert!(!file_contains_definition(
1569            &rust_path,
1570            rust_content,
1571            "NonExistent"
1572        ));
1573
1574        // Test Python class definition
1575        let python_content = r#"
1576            class MyClass:
1577                def __init__(self):
1578                    pass
1579                    
1580            def my_function():
1581                pass
1582        "#;
1583
1584        let python_path = PathBuf::from("test.py");
1585        assert!(file_contains_definition(
1586            &python_path,
1587            python_content,
1588            "MyClass"
1589        ));
1590        assert!(file_contains_definition(
1591            &python_path,
1592            python_content,
1593            "my_function"
1594        ));
1595        assert!(!file_contains_definition(
1596            &python_path,
1597            python_content,
1598            "NonExistent"
1599        ));
1600
1601        // Test TypeScript interface definition
1602        let typescript_content = r#"
1603            export interface MyInterface {
1604                prop1: string;
1605                prop2: number;
1606            }
1607            
1608            export type MyType = string | number;
1609            
1610            export class MyClass {
1611                constructor() {}
1612            }
1613        "#;
1614
1615        let typescript_path = PathBuf::from("test.ts");
1616        assert!(file_contains_definition(
1617            &typescript_path,
1618            typescript_content,
1619            "MyInterface"
1620        ));
1621        assert!(file_contains_definition(
1622            &typescript_path,
1623            typescript_content,
1624            "MyType"
1625        ));
1626        assert!(file_contains_definition(
1627            &typescript_path,
1628            typescript_content,
1629            "MyClass"
1630        ));
1631        assert!(!file_contains_definition(
1632            &typescript_path,
1633            typescript_content,
1634            "NonExistent"
1635        ));
1636    }
1637
1638    #[test]
1639    fn test_query_engine_rust() {
1640        use crate::core::semantic::query_engine::QueryEngine;
1641        use tree_sitter::Parser;
1642
1643        let rust_content = r#"
1644            use model::{Account, DatabaseFactory, Rule, RuleLevel, RuleName};
1645            
1646            pub fn create(
1647                database: &mut dyn DatabaseFactory,
1648                account: &Account,
1649                rule_name: &RuleName,
1650            ) -> Result<Rule, Box<dyn std::error::Error>> {
1651                Ok(Rule::new())
1652            }
1653        "#;
1654
1655        let language = tree_sitter_rust::language();
1656        let query_engine = QueryEngine::new(language, "rust").unwrap();
1657
1658        let mut parser = Parser::new();
1659        parser.set_language(tree_sitter_rust::language()).unwrap();
1660
1661        let result = query_engine
1662            .analyze_with_parser(&mut parser, rust_content)
1663            .unwrap();
1664
1665        println!("Imports found: {:?}", result.imports);
1666        println!("Type references found: {:?}", result.type_references);
1667
1668        // Should find imports
1669        assert!(!result.imports.is_empty(), "Should find imports");
1670
1671        // Should find type references from the imports
1672        assert!(
1673            !result.type_references.is_empty(),
1674            "Should find type references"
1675        );
1676
1677        // Check specific types
1678        let type_names: Vec<&str> = result
1679            .type_references
1680            .iter()
1681            .map(|t| t.name.as_str())
1682            .collect();
1683        assert!(
1684            type_names.contains(&"DatabaseFactory"),
1685            "Should find DatabaseFactory type"
1686        );
1687        assert!(type_names.contains(&"Account"), "Should find Account type");
1688        assert!(
1689            type_names.contains(&"RuleName"),
1690            "Should find RuleName type"
1691        );
1692    }
1693}