use std::collections::HashMap;
use tracing::debug;
use crate::error::ScanError;
use crate::model::DepLevel;
use crate::port::ScanStore;
use super::types::{CrossFileMatch, ImportResolutionMap, UnresolvedRef};
fn classify_ref(
uref: &UnresolvedRef,
file_import_names: &HashMap<(i64, String), String>,
import_resolution: &ImportResolutionMap,
) -> Option<CrossFileMatch> {
if let Some(imp_path) = file_import_names.get(&(uref.source_file_id, uref.target_name.clone()))
{
if let Some((target_fid, name_map)) =
import_resolution.get(&(uref.source_file_id, imp_path.clone()))
{
if let Some(&target_sym_id) = name_map.get(&uref.target_name) {
return Some(CrossFileMatch {
source_file_id: uref.source_file_id,
source_symbol_id: uref.source_symbol_id,
target_file_id: *target_fid,
target_symbol_id: target_sym_id,
dep_type: uref.dep_type,
});
}
}
}
for ((src_fid, _), (target_fid, name_map)) in import_resolution {
if *src_fid == uref.source_file_id {
if let Some(&target_sym_id) = name_map.get(&uref.target_name) {
return Some(CrossFileMatch {
source_file_id: uref.source_file_id,
source_symbol_id: uref.source_symbol_id,
target_file_id: *target_fid,
target_symbol_id: target_sym_id,
dep_type: uref.dep_type,
});
}
}
}
None
}
pub(super) fn resolve_cross_file(
unresolved_refs: &[UnresolvedRef],
file_import_names: &HashMap<(i64, String), String>,
import_resolution: &ImportResolutionMap,
db: &dyn ScanStore,
) -> Result<i64, ScanError> {
let mut resolved = Vec::new();
for uref in unresolved_refs {
match classify_ref(uref, file_import_names, import_resolution) {
Some(m) => resolved.push(m),
None => debug!(
"unresolved cross-file ref: {} in file_id={} (dep_type={})",
uref.target_name, uref.source_file_id, uref.dep_type
),
}
}
resolved.iter().try_fold(0i64, |count, m| {
db.insert_dependency(
m.source_file_id,
Some(m.source_symbol_id),
Some(m.target_file_id),
Some(m.target_symbol_id),
m.dep_type,
DepLevel::Symbol,
None,
)?;
Ok(count + 1)
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::DepType;
fn make_uref(file_id: i64, sym_id: i64, name: &str, dep_type: DepType) -> UnresolvedRef {
UnresolvedRef {
source_file_id: file_id,
source_symbol_id: sym_id,
target_name: name.to_string(),
dep_type,
}
}
#[test]
fn test_classify_ref_named_import_hit() {
let mut file_import_names = HashMap::new();
file_import_names.insert((1, "Config".to_string()), "./config".to_string());
let mut name_map = HashMap::new();
name_map.insert("Config".to_string(), 42i64);
let mut import_resolution: ImportResolutionMap = HashMap::new();
import_resolution.insert((1, "./config".to_string()), (2, name_map));
let uref = make_uref(1, 10, "Config", DepType::TypeRef);
let result = classify_ref(&uref, &file_import_names, &import_resolution);
assert!(result.is_some());
let m = result.unwrap();
assert_eq!(m.target_file_id, 2);
assert_eq!(m.target_symbol_id, 42);
assert_eq!(m.dep_type, DepType::TypeRef);
}
#[test]
fn test_classify_ref_named_import_miss() {
let mut file_import_names = HashMap::new();
file_import_names.insert((1, "Config".to_string()), "./config".to_string());
let mut name_map = HashMap::new();
name_map.insert("OtherSymbol".to_string(), 42i64);
let mut import_resolution: ImportResolutionMap = HashMap::new();
import_resolution.insert((1, "./config".to_string()), (2, name_map));
let uref = make_uref(1, 10, "Config", DepType::TypeRef);
let result = classify_ref(&uref, &file_import_names, &import_resolution);
assert!(result.is_none());
}
#[test]
fn test_classify_ref_wildcard_fallback() {
let file_import_names: HashMap<(i64, String), String> = HashMap::new();
let mut name_map = HashMap::new();
name_map.insert("Helper".to_string(), 99i64);
let mut import_resolution: ImportResolutionMap = HashMap::new();
import_resolution.insert((1, "crate::utils::*".to_string()), (3, name_map));
let uref = make_uref(1, 10, "Helper", DepType::Calls);
let result = classify_ref(&uref, &file_import_names, &import_resolution);
assert!(result.is_some());
let m = result.unwrap();
assert_eq!(m.target_file_id, 3);
assert_eq!(m.target_symbol_id, 99);
}
#[test]
fn test_classify_ref_no_match() {
let file_import_names: HashMap<(i64, String), String> = HashMap::new();
let import_resolution: ImportResolutionMap = HashMap::new();
let uref = make_uref(1, 10, "Unknown", DepType::Calls);
let result = classify_ref(&uref, &file_import_names, &import_resolution);
assert!(result.is_none());
}
}