1mod config;
2mod cross_file_deps;
3pub(crate) mod hasher;
4mod imports;
5mod indexing;
6mod reconcile;
7mod same_file_deps;
8mod types;
9
10use std::path::Path;
11
12use tracing::info;
13
14use crate::error::ScanError;
15use crate::port::{FileDiscovery, LanguageRegistry, ScanStore};
16
17pub use types::ScanResult;
18
19pub fn scan(
21 project_dir: &Path,
22 db: &dyn ScanStore,
23 full: bool,
24 lang: &dyn LanguageRegistry,
25 discovery: &dyn FileDiscovery,
26) -> Result<ScanResult, ScanError> {
27 let project_dir = project_dir
28 .canonicalize()
29 .unwrap_or_else(|_| project_dir.to_path_buf());
30
31 let files = discovery.discover_files(&project_dir, lang.supported_extensions())?;
32 let scan_id = db.begin_scan()?;
33
34 info!("discovered {} source files", files.len());
35
36 let force_full = full || config::config_changed(&project_dir, db, lang.config_files());
38
39 let indexing = indexing::index_files(&project_dir, &files, db, scan_id, force_full, lang)?;
41
42 reconcile::reconcile_deleted_files(&project_dir, &files, db)?;
44
45 let (import_resolution, import_deps) =
47 imports::resolve(&project_dir, &indexing.pending_imports, db, lang)?;
48
49 let cross_file_deps = cross_file_deps::resolve_cross_file(
51 &indexing.unresolved_refs,
52 &indexing.file_import_names,
53 &import_resolution,
54 db,
55 )?;
56
57 let result = ScanResult {
58 files_total: indexing.files_total,
59 files_skipped: indexing.files_skipped,
60 files_partial: indexing.files_partial,
61 symbols: indexing.symbols,
62 dependencies: indexing.same_file_deps + import_deps + cross_file_deps,
63 };
64
65 db.complete_scan(
66 scan_id,
67 result.files_total,
68 result.symbols,
69 result.dependencies,
70 )?;
71
72 config::save_config_hash(&project_dir, db, lang.config_files());
74
75 info!(
76 "scan complete: {} files, {} symbols, {} deps",
77 result.files_total, result.symbols, result.dependencies
78 );
79
80 Ok(result)
81}