use rustc_hash::{FxHashMap, FxHashSet};
use fallow_types::extract::ModuleInfo;
use crate::discover::FileId;
use crate::graph::ModuleGraph;
use crate::resolve::{ResolveResult, ResolvedModule};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(super) struct CompKey {
pub file: FileId,
pub name: String,
}
pub(super) struct ChildResolver<'a> {
components_per_file: FxHashMap<FileId, FxHashSet<&'a str>>,
import_targets: FxHashMap<FileId, FxHashMap<&'a str, FileId>>,
sole_component: FxHashMap<FileId, &'a str>,
}
impl<'a> ChildResolver<'a> {
pub(super) fn new(
graph: &'a ModuleGraph,
modules_by_id: &FxHashMap<FileId, &'a ModuleInfo>,
resolved_by_id: &FxHashMap<FileId, &'a ResolvedModule>,
) -> Self {
let mut components_per_file: FxHashMap<FileId, FxHashSet<&'a str>> = FxHashMap::default();
let mut sole_component: FxHashMap<FileId, &'a str> = FxHashMap::default();
for node in &graph.modules {
let Some(module) = modules_by_id.get(&node.file_id) else {
continue;
};
if module.component_functions.is_empty() {
continue;
}
let set: FxHashSet<&'a str> = module
.component_functions
.iter()
.map(|c| c.name.as_str())
.collect();
if module.component_functions.len() == 1 {
sole_component.insert(node.file_id, module.component_functions[0].name.as_str());
}
components_per_file.insert(node.file_id, set);
}
let mut import_targets: FxHashMap<FileId, FxHashMap<&'a str, FileId>> =
FxHashMap::default();
for (file, resolved) in resolved_by_id {
let mut map: FxHashMap<&'a str, FileId> = FxHashMap::default();
for import in &resolved.resolved_imports {
if let ResolveResult::InternalModule(target)
| ResolveResult::InternalPackageModule {
file_id: target, ..
} = &import.target
{
let local = import.info.local_name.as_str();
if !local.is_empty() {
map.insert(local, *target);
}
}
}
import_targets.insert(*file, map);
}
Self {
components_per_file,
import_targets,
sole_component,
}
}
pub(super) fn resolve(&self, parent_file: FileId, child_name: &str) -> Option<CompKey> {
if child_name.contains('.') {
return None;
}
if self
.components_per_file
.get(&parent_file)
.is_some_and(|set| set.contains(child_name))
{
return Some(CompKey {
file: parent_file,
name: child_name.to_string(),
});
}
let target = *self.import_targets.get(&parent_file)?.get(child_name)?;
if self
.components_per_file
.get(&target)
.is_some_and(|set| set.contains(child_name))
{
return Some(CompKey {
file: target,
name: child_name.to_string(),
});
}
if let Some(&sole) = self.sole_component.get(&target) {
return Some(CompKey {
file: target,
name: sole.to_string(),
});
}
None
}
pub(super) fn is_imported_binding(&self, parent_file: FileId, child_name: &str) -> bool {
if child_name.contains('.') {
return false;
}
self.import_targets
.get(&parent_file)
.is_some_and(|map| map.contains_key(child_name))
}
}