use oxc_index::IndexVec;
use rolldown_common::{
ChunkIdx, ExternalInteropUse, ImportRecordIdx, ModuleIdx, NamedImport, NormalModule,
PostChunkOptimizationOperation, Specifier, SymbolRef,
};
use rustc_hash::FxHashMap;
use crate::{chunk_graph::ChunkGraph, stages::link_stage::LinkStageOutput};
pub fn specifier_needs_interop(specifier: &Specifier) -> bool {
matches!(specifier, Specifier::Star)
|| matches!(specifier, Specifier::Literal(name) if name.as_str() == "default")
}
pub fn external_import_needs_interop(
named_imports: &[(rolldown_common::ModuleIdx, NamedImport)],
) -> bool {
named_imports.iter().any(|(_, import)| specifier_needs_interop(&import.imported))
}
pub fn import_record_needs_interop(module: &NormalModule, rec_idx: ImportRecordIdx) -> bool {
module
.named_imports
.values()
.any(|import| import.record_idx == rec_idx && specifier_needs_interop(&import.imported))
}
pub fn chunk_recorded_external_interop(
link_output: &LinkStageOutput,
assignments: ChunkAssignments<'_>,
chunk_idx: ChunkIdx,
external_namespace_ref: SymbolRef,
) -> Option<ExternalInteropUse> {
let canonical_ref = link_output.symbol_db.canonical_ref_for(external_namespace_ref);
let observers = link_output.used_external_symbols.interop_uses_by_observer(&canonical_ref)?;
observers
.iter()
.filter(|(observer, _)| {
match assignments.live_chunk_of(**observer) {
Some(observer_chunk) => observer_chunk == chunk_idx,
None => true,
}
})
.map(|(_, use_)| *use_)
.reduce(ExternalInteropUse::union)
}
pub fn chunk_external_interop_modes(
link_output: &LinkStageOutput,
assignments: ChunkAssignments<'_>,
chunk_idx: ChunkIdx,
external_namespace_ref: SymbolRef,
named_imports: Option<&[(ModuleIdx, NamedImport)]>,
) -> Option<ExternalInteropUse> {
let recorded =
chunk_recorded_external_interop(link_output, assignments, chunk_idx, external_namespace_ref);
let mut modes = recorded.unwrap_or_default();
let mut any = recorded.is_some();
for (importer_idx, import) in named_imports.unwrap_or_default() {
if !specifier_needs_interop(&import.imported) {
continue;
}
any = true;
if link_output.module_table[*importer_idx]
.as_normal()
.is_some_and(NormalModule::should_consider_node_esm_spec_for_static_import)
{
modes.node_esm = true;
} else {
modes.non_node_esm = true;
}
if modes.node_esm && modes.non_node_esm {
break;
}
}
any.then_some(modes)
}
pub fn chunk_has_node_esm_reader(
link_output: &LinkStageOutput,
assignments: ChunkAssignments<'_>,
chunk_idx: ChunkIdx,
external_namespace_ref: SymbolRef,
named_imports: Option<&[(ModuleIdx, NamedImport)]>,
) -> bool {
let is_node_esm = |module_idx: ModuleIdx| {
link_output.module_table[module_idx]
.as_normal()
.is_some_and(NormalModule::should_consider_node_esm_spec_for_static_import)
};
let canonical_ref = link_output.symbol_db.canonical_ref_for(external_namespace_ref);
let observer_reads_as_node = link_output
.used_external_symbols
.interop_uses_by_observer(&canonical_ref)
.is_some_and(|observers| {
observers.keys().any(|observer| {
let could_be_in_chunk = match assignments.live_chunk_of(*observer) {
Some(observer_chunk) => observer_chunk == chunk_idx,
None => true,
};
could_be_in_chunk && is_node_esm(*observer)
})
});
observer_reads_as_node
|| named_imports.unwrap_or_default().iter().any(|(importer_idx, import)| {
specifier_needs_interop(&import.imported) && is_node_esm(*importer_idx)
})
}
#[derive(Clone, Copy)]
pub struct ChunkAssignments<'a> {
module_to_chunk: &'a IndexVec<ModuleIdx, Option<ChunkIdx>>,
chunk_operations: &'a FxHashMap<ChunkIdx, PostChunkOptimizationOperation>,
}
impl<'a> ChunkAssignments<'a> {
pub fn new(
module_to_chunk: &'a IndexVec<ModuleIdx, Option<ChunkIdx>>,
chunk_operations: &'a FxHashMap<ChunkIdx, PostChunkOptimizationOperation>,
) -> Self {
Self { module_to_chunk, chunk_operations }
}
pub fn from_graph(chunk_graph: &'a ChunkGraph) -> Self {
Self::new(&chunk_graph.module_to_chunk, &chunk_graph.post_chunk_optimization_operations)
}
fn live_chunk_of(&self, module_idx: ModuleIdx) -> Option<ChunkIdx> {
self.module_to_chunk.get(module_idx).copied().flatten().filter(|chunk_idx| {
self.chunk_operations.get(chunk_idx) != Some(&PostChunkOptimizationOperation::Removed)
})
}
}