use rolldown_common::{
AstScopes, Chunk, ChunkIdx, ConstExportMeta, ImportRecordIdx, IndexModules, ModuleIdx,
ModuleType, NormalModule, PathsOutputOption, RenderedConcatenatedModuleParts,
RetainedExportSymbols, RuntimeModuleBrief, SharedFileEmitter, StmtInfos, SymbolRef, SymbolRefDb,
UsedSymbolRefs, WrapKind,
};
pub type FinalizerMutableFields = (
FxIndexMap<ImportRecordIdx, String>, RenderedConcatenatedModuleParts, Vec<BuildDiagnostic>, );
use oxc::ast::builder::AstBuilder;
use oxc::ast_visit::VisitJsMut as _;
use oxc::semantic::NodeId;
use rolldown_ecmascript::EcmaAst;
use rolldown_error::{BuildDiagnostic, CausedPlugin};
use rolldown_plugin::HookResolveFileUrlOutput;
use rolldown_utils::indexmap::{FxIndexMap, FxIndexSet};
use rustc_hash::{FxHashMap, FxHashSet};
use crate::{
SharedOptions,
chunk_graph::ChunkGraph,
module_finalizers::{ScopeHoistingFinalizer, TraverseState},
stages::{
generate_stage::{
FinalEsmInitMetadata, Sealed,
order_wrap_state::{EsmInitOrigin, EsmInitTarget, OrderWrapState},
},
link_stage::SafelyMergeCjsNsInfo,
},
types::linking_metadata::{LinkingMetadata, LinkingMetadataVec},
};
pub struct ScopeHoistingFinalizerContext<'me> {
pub idx: ModuleIdx,
pub chunk: &'me Chunk,
pub chunk_idx: ChunkIdx,
pub module: &'me NormalModule,
pub stmt_infos: &'me StmtInfos,
pub index_stmt_infos: &'me crate::type_alias::IndexStmtInfos,
pub modules: &'me IndexModules,
pub linking_info: &'me LinkingMetadata,
pub linking_infos: &'me LinkingMetadataVec,
pub order_wrap_state: &'me OrderWrapState,
pub final_esm_init_metadata: &'me Sealed<FinalEsmInitMetadata>,
pub used_symbol_refs: &'me UsedSymbolRefs,
pub symbol_db: &'me SymbolRefDb,
pub runtime: &'me RuntimeModuleBrief,
pub chunk_graph: &'me ChunkGraph,
pub options: &'me SharedOptions,
pub file_emitter: &'me SharedFileEmitter,
pub constant_value_map: &'me FxHashMap<SymbolRef, ConstExportMeta>,
pub safely_merge_cjs_ns_map: &'me FxHashMap<ModuleIdx, SafelyMergeCjsNsInfo>,
pub retained_export_symbols: &'me RetainedExportSymbols,
pub resolved_paths: Option<&'me PathsOutputOption>,
pub resolved_file_urls: &'me FxHashMap<(ModuleIdx, NodeId), HookResolveFileUrlOutput>,
pub has_enum_inlining: bool,
}
#[derive(Clone, Copy, Debug)]
pub(super) enum ModuleWrapperMode {
None,
InteropCjs(SymbolRef),
InteropEsm(EsmInitTarget),
ExecutionOrder(EsmInitTarget),
}
impl<'me> ScopeHoistingFinalizerContext<'me> {
pub(super) fn wrapper_mode(&self) -> ModuleWrapperMode {
let legacy_wrapper_is_included = self
.linking_info
.wrapper_stmt_info
.is_some_and(|stmt_idx| self.linking_info.stmt_info_included.has_bit(stmt_idx));
if matches!(self.linking_info.wrap_kind(), WrapKind::Cjs) && legacy_wrapper_is_included {
return ModuleWrapperMode::InteropCjs(
self.linking_info.wrapper_ref.expect("included CJS wrapper should have a symbol"),
);
}
let Some(target) = self.order_wrap_state.esm_init_target(self.idx, self.linking_info) else {
return ModuleWrapperMode::None;
};
let declaration_is_included = match target.origin {
EsmInitOrigin::Interop => legacy_wrapper_is_included,
EsmInitOrigin::ExecutionOrder => {
self.order_wrap_state.order_wrapper_chunk(self.idx) == Some(self.chunk_idx)
}
};
if !declaration_is_included {
return ModuleWrapperMode::None;
}
match target.origin {
EsmInitOrigin::Interop => ModuleWrapperMode::InteropEsm(target),
EsmInitOrigin::ExecutionOrder => ModuleWrapperMode::ExecutionOrder(target),
}
}
#[tracing::instrument(level = "trace", skip_all)]
pub fn finalize_normal_module(
self,
ast: &'me mut EcmaAst,
ast_scope: &'me AstScopes,
) -> FinalizerMutableFields {
ast.program.with_mut(move |fields| {
let (oxc_program, alloc) = (fields.program, fields.allocator);
let module_namespace_included = self.linking_info.namespace_included;
let need_inline_json_prop = matches!(self.module.module_type, ModuleType::Json)
&& !self.module.exports_kind.is_commonjs()
&& !module_namespace_included;
let transferred_import_record = self
.chunk
.remove_map
.get(&self.idx)
.cloned()
.map(|idxs| idxs.into_iter().map(|idx| (idx, String::new())).collect::<FxIndexMap<_, _>>())
.unwrap_or_default();
let mut finalizer = ScopeHoistingFinalizer {
ctx: self,
scope: ast_scope,
ast_builder: AstBuilder::new(alloc),
generated_init_esm_importee_ids: FxHashSet::default(),
generated_order_cjs_carriers: FxHashSet::default(),
scope_stack: vec![],
top_level_var_bindings: FxIndexSet::default(),
state: TraverseState::empty(),
cur_stmt_index: 0,
keep_name_statement_to_insert: vec![],
needs_hosted_top_level_binding: false,
module_namespace_included,
transferred_import_record,
rendered_concatenated_wrapped_module_parts: RenderedConcatenatedModuleParts::default(),
json_module_inlined_prop: need_inline_json_prop.then(|| Box::new(FxHashMap::default())),
missing_file_reference_ids: FxIndexMap::default(),
resolve_file_url_errors: Vec::new(),
surviving_import_meta_spans: FxIndexMap::default(),
};
finalizer.visit_program(oxc_program);
let mut diagnostics = {
let module = finalizer.ctx.module;
finalizer
.surviving_import_meta_spans
.iter()
.map(|(span, kind)| {
BuildDiagnostic::empty_import_meta(
module.id.to_string(),
module.ecma_view.source.clone(),
*span,
finalizer.ctx.options.format.as_str().into(),
*kind,
)
.with_severity_warning()
})
.collect::<Vec<_>>()
};
let missing_file_reference_ids = finalizer.missing_file_reference_ids;
if !missing_file_reference_ids.is_empty() {
let module = finalizer.ctx.module;
diagnostics.extend(missing_file_reference_ids.into_iter().map(|(reference_id, span)| {
BuildDiagnostic::file_not_found(
reference_id.as_str(),
module.id.to_string(),
module.ecma_view.source.clone(),
span,
)
}));
}
let mut resolve_file_url_errors = finalizer.resolve_file_url_errors;
if !resolve_file_url_errors.is_empty() {
resolve_file_url_errors.sort_unstable();
resolve_file_url_errors.dedup();
diagnostics.extend(resolve_file_url_errors.into_iter().map(|(plugin_name, message)| {
BuildDiagnostic::plugin_error(CausedPlugin::new(plugin_name), anyhow::anyhow!(message))
}));
}
(
finalizer.transferred_import_record,
finalizer.rendered_concatenated_wrapped_module_parts,
diagnostics,
)
})
}
}