use rustc_hash::FxHashMap;
use fallow_types::discover::FileId;
use fallow_types::extract::{ImportedName, NamespaceObjectAlias};
use crate::resolve::{ResolveResult, ResolvedModule};
use super::ModuleGraph;
use super::narrowing::{
create_synthetic_exports_for_star_re_exports, mark_member_exports_referenced,
};
use super::types::ReferenceKind;
struct PendingCredit {
target_module_idx: usize,
member: String,
consumer_file_id: FileId,
import_span: oxc_span::Span,
}
pub(super) fn propagate_cross_package_aliases(
graph: &mut ModuleGraph,
module_by_id: &FxHashMap<FileId, &ResolvedModule>,
) {
let pending = collect_pending_credits(graph, module_by_id);
apply_pending_credits(graph, &pending);
}
fn collect_pending_credits(
graph: &ModuleGraph,
module_by_id: &FxHashMap<FileId, &ResolvedModule>,
) -> Vec<PendingCredit> {
let mut pending = Vec::new();
for alias_module in module_by_id.values() {
if alias_module.namespace_object_aliases.is_empty() {
continue;
}
let alias_file_id = alias_module.file_id;
for alias in &alias_module.namespace_object_aliases {
let Some(namespace_target_id) = resolve_namespace_target(alias_module, alias) else {
continue;
};
let Some(target_module_idx) = module_index_for_file(graph, namespace_target_id) else {
continue;
};
collect_credits_for_alias(
module_by_id,
alias_file_id,
alias,
target_module_idx,
&mut pending,
);
}
}
pending
}
fn resolve_namespace_target(
alias_module: &ResolvedModule,
alias: &NamespaceObjectAlias,
) -> Option<FileId> {
alias_module.resolved_imports.iter().find_map(|import| {
if import.info.local_name != alias.namespace_local {
return None;
}
if !matches!(import.info.imported_name, ImportedName::Namespace) {
return None;
}
match &import.target {
ResolveResult::InternalModule(file_id) => Some(*file_id),
_ => None,
}
})
}
fn module_index_for_file(graph: &ModuleGraph, file_id: FileId) -> Option<usize> {
let idx = file_id.0 as usize;
if idx >= graph.modules.len() {
return None;
}
Some(idx)
}
fn collect_credits_for_alias(
module_by_id: &FxHashMap<FileId, &ResolvedModule>,
alias_file_id: FileId,
alias: &NamespaceObjectAlias,
target_module_idx: usize,
pending: &mut Vec<PendingCredit>,
) {
let prefix_match = format!(".{}", alias.suffix);
for consumer in module_by_id.values() {
if consumer.file_id == alias_file_id {
continue;
}
for import in &consumer.resolved_imports {
if !matches!(&import.target, ResolveResult::InternalModule(file_id) if *file_id == alias_file_id)
{
continue;
}
let imported_matches = match &import.info.imported_name {
ImportedName::Named(n) => n == &alias.via_export_name,
ImportedName::Default => alias.via_export_name == "default",
_ => false,
};
if !imported_matches {
continue;
}
let consumer_local = import.info.local_name.as_str();
if consumer_local.is_empty() {
continue;
}
let expected_object = format!("{consumer_local}{prefix_match}");
for access in &consumer.member_accesses {
if access.object != expected_object {
continue;
}
pending.push(PendingCredit {
target_module_idx,
member: access.member.clone(),
consumer_file_id: consumer.file_id,
import_span: import.info.span,
});
}
}
}
}
fn apply_pending_credits(graph: &mut ModuleGraph, pending: &[PendingCredit]) {
type GroupKey = (usize, FileId, oxc_span::Span);
let mut groups: FxHashMap<GroupKey, Vec<String>> = FxHashMap::default();
for credit in pending {
groups
.entry((
credit.target_module_idx,
credit.consumer_file_id,
credit.import_span,
))
.or_default()
.push(credit.member.clone());
}
for ((target_module_idx, consumer_file_id, import_span), members) in groups {
let module = &mut graph.modules[target_module_idx];
let found_members = mark_member_exports_referenced(
&mut module.exports,
consumer_file_id,
&members,
import_span,
ReferenceKind::NamespaceImport,
);
create_synthetic_exports_for_star_re_exports(
&mut module.exports,
&module.re_exports,
consumer_file_id,
&members,
&found_members,
import_span,
);
}
}