use oxc_str::CompactStr;
use crate::{
stages::{generate_stage::order_wrap_state::OrderWrapState, link_stage::LinkStageOutput},
utils::{
external_import_interop::{
ChunkAssignments, chunk_external_interop_modes, chunk_has_node_esm_reader,
},
renamer::{NestedScopeRenamer, Renamer},
},
};
use arcstr::ArcStr;
use rolldown_common::{Chunk, ChunkIdx, ChunkKind, GetLocalDb, OutputFormat, SymbolRef, WrapKind};
use rolldown_utils::ecmascript::legitimize_identifier_name;
use rustc_hash::{FxHashMap, FxHashSet};
#[tracing::instrument(level = "trace", skip_all)]
#[expect(clippy::too_many_arguments)]
pub fn deconflict_chunk_symbols(
chunk_idx: ChunkIdx,
chunk: &mut Chunk,
link_output: &LinkStageOutput,
order_wrap_state: &OrderWrapState,
order_live_symbols: &FxHashSet<SymbolRef>,
format: OutputFormat,
index_chunk_id_to_name: &FxHashMap<ChunkIdx, ArcStr>,
chunk_assignments: ChunkAssignments<'_>,
) {
let mut renamer = Renamer::new(chunk.entry_module_idx(), &link_output.symbol_db, format);
chunk
.modules
.iter()
.copied()
.filter_map(|idx| {
Some(
link_output.symbol_db[idx]
.as_ref()?
.ast_scopes
.scoping()
.root_unresolved_references()
.keys(),
)
})
.flatten()
.for_each(|name| {
renamer.reserve(CompactStr::new(name));
});
if matches!(format, OutputFormat::Iife | OutputFormat::Umd | OutputFormat::Cjs) {
chunk
.direct_imports_from_external_modules
.iter()
.map(|(idx, _)| *idx)
.chain(chunk.entry_level_external_module_idx.iter().copied())
.filter_map(|idx| link_output.module_table[idx].as_external())
.for_each(|external_module| {
renamer.add_symbol_in_root_scope(external_module.namespace_ref, true);
});
chunk
.import_symbol_from_external_modules
.iter()
.filter_map(|idx| link_output.module_table[*idx].as_external())
.for_each(|external_module| {
renamer.add_symbol_in_root_scope(external_module.namespace_ref, true);
});
}
match chunk.kind {
ChunkKind::EntryPoint { module, .. } => {
let meta = &link_output.metas[module];
meta.referenced_symbols_by_entry_point_chunk.iter().for_each(
|(symbol_ref, came_from_cjs)| {
if !came_from_cjs {
renamer.add_symbol_in_root_scope(*symbol_ref, true);
}
},
);
}
ChunkKind::Common => {}
}
if matches!(format, OutputFormat::Esm) {
chunk.direct_imports_from_external_modules.iter().for_each(|(module, _)| {
let db = link_output.symbol_db.local_db(*module);
db.classic_data.iter_enumerated().for_each(|(symbol, _)| {
let symbol_ref = (*module, symbol).into();
if link_output.used_external_symbols.contains(&symbol_ref) {
renamer.add_symbol_in_root_scope(symbol_ref, true);
}
});
});
}
let chunk_scope_captured_names = collect_chunk_scope_captured_names(
chunk_idx,
chunk,
link_output,
order_wrap_state,
order_live_symbols,
format,
&renamer,
);
debug_assert!(
chunk
.modules
.iter()
.filter_map(|idx| link_output.module_table[*idx].as_normal().map(|m| m.exec_order))
.is_sorted(),
"chunk.modules must be in ascending exec_order for deconfliction"
);
chunk
.modules
.iter()
.copied()
.rev()
.filter_map(|id| link_output.module_table[id].as_normal())
.for_each(|module| {
if let Some(hmr_hot_ref) = module.hmr_hot_ref {
renamer.add_symbol_in_root_scope(hmr_hot_ref, true);
}
let meta = &link_output.metas[module.idx];
let is_cjs_wrapped_module = matches!(meta.wrap_kind(), WrapKind::Cjs);
link_output.stmt_infos[module.idx]
.iter_enumerated()
.filter(|(idx, stmt_info)| {
(meta.stmt_info_included.has_bit(*idx)
|| order_wrap_state.forces_runtime_stmt(&link_output.runtime, module.idx, stmt_info))
&& !stmt_info.import_records.iter().any(|rec_idx| {
order_wrap_state.has_order_cjs_carrier(
crate::stages::generate_stage::order_wrap_state::OrderCjsCarrierKey {
importer: module.idx,
record: *rec_idx,
},
)
})
})
.for_each(|(_, stmt_info)| {
for declared_symbol in stmt_info.declared_symbols.iter().filter(|item| item.is_normal()) {
let symbol_ref = declared_symbol.inner();
let canonical_ref = link_output.symbol_db.canonical_ref_for(symbol_ref);
if canonical_ref.owner != module.idx {
continue;
}
let needs_deconflict = if is_cjs_wrapped_module {
let is_facade_or_external = link_output.symbol_db.is_facade_symbol(canonical_ref)
|| stmt_info.import_records.iter().any(|import_rec_idx| {
module.import_records[*import_rec_idx]
.resolved_module
.is_some_and(|module_idx| link_output.module_table[module_idx].is_external())
});
let shadows_chunk_scope_name =
chunk_scope_captured_names.contains(canonical_ref.name(&link_output.symbol_db));
is_facade_or_external || shadows_chunk_scope_name
} else {
true
};
renamer.add_symbol_in_root_scope(symbol_ref, needs_deconflict);
}
});
});
for synthetic in order_wrap_state.synthetic_statements_for_chunk(chunk_idx) {
for declared_symbol in synthetic.declared_symbols.iter().filter(|item| item.is_normal()) {
debug_assert_eq!(declared_symbol.inner().owner, synthetic.owner);
renamer.add_symbol_in_root_scope(declared_symbol.inner(), true);
}
}
chunk.imports_from_other_chunks.iter().flat_map(|(_, items)| items.iter()).for_each(|item| {
renamer.add_symbol_in_root_scope(item.import_ref, true);
});
chunk.require_binding_names_for_other_chunks = chunk
.imports_from_other_chunks
.iter()
.map(|(id, _)| {
(
*id,
renamer
.create_conflictless_name(&legitimize_identifier_name(&format!(
"require_{}",
index_chunk_id_to_name[id]
)))
.to_string(),
)
})
.collect();
if matches!(format, OutputFormat::Iife | OutputFormat::Umd | OutputFormat::Cjs) {
let mut node_mode_names = FxHashMap::default();
let indirect_externals = matches!(format, OutputFormat::Cjs)
.then(|| chunk.import_symbol_from_external_modules.iter())
.into_iter()
.flatten();
let externals = chunk
.direct_imports_from_external_modules
.iter()
.map(|(ext_idx, named_imports)| (*ext_idx, Some(named_imports.as_slice())))
.chain(indirect_externals.map(|ext_idx| (*ext_idx, None)));
for (ext_idx, named_imports) in externals {
let ext =
link_output.module_table[ext_idx].as_external().expect("Should be external module here");
let Some(modes) = chunk_external_interop_modes(
link_output,
chunk_assignments,
chunk_idx,
ext.namespace_ref,
named_imports,
) else {
continue;
};
if modes.node_esm
&& modes.non_node_esm
&& chunk_has_node_esm_reader(
link_output,
chunk_assignments,
chunk_idx,
ext.namespace_ref,
named_imports,
)
{
let canonical_ref = link_output.symbol_db.canonical_ref_for(ext.namespace_ref);
let original_name = canonical_ref.name(&link_output.symbol_db);
let node_name = renamer.create_conflictless_name(original_name);
node_mode_names.insert(canonical_ref, node_name);
}
}
chunk.node_mode_external_ns_names = node_mode_names;
}
rename_shadowing_symbols_in_nested_scopes(chunk, link_output, format, &mut renamer);
chunk.canonical_names = renamer.into_canonical_names();
}
fn collect_chunk_scope_captured_names(
chunk_idx: ChunkIdx,
chunk: &Chunk,
link_output: &LinkStageOutput,
order_wrap_state: &OrderWrapState,
order_live_symbols: &FxHashSet<SymbolRef>,
format: OutputFormat,
renamer: &Renamer<'_>,
) -> FxHashSet<CompactStr> {
let mut captured: FxHashSet<CompactStr> = FxHashSet::default();
for synthetic in order_wrap_state.synthetic_statements_for_chunk(chunk_idx) {
for declared in &synthetic.declared_symbols {
captured.insert(CompactStr::new(declared.inner().name(&link_output.symbol_db)));
}
}
if matches!(format, OutputFormat::Iife | OutputFormat::Umd) {
for (external_idx, _) in &chunk.direct_imports_from_external_modules {
let Some(external) = link_output.module_table[*external_idx].as_external() else {
continue;
};
if let Some(name) = renamer.get_canonical_name(external.namespace_ref) {
captured.insert(name.clone());
}
}
}
for module_idx in chunk.modules.iter().copied() {
if let Some(wrapper_ref) = link_output.metas[module_idx].wrapper_ref {
let canonical_ref = link_output.symbol_db.canonical_ref_for(wrapper_ref);
captured.insert(CompactStr::new(canonical_ref.name(&link_output.symbol_db)));
}
}
if matches!(format, OutputFormat::Esm) {
for item in chunk.imports_from_other_chunks.values().flatten() {
let canonical_ref = link_output.symbol_db.canonical_ref_for(item.import_ref);
let is_cjs_wrapper =
link_output.metas[canonical_ref.owner].wrapper_ref.is_some_and(|wrapper_ref| {
link_output.symbol_db.canonical_ref_for(wrapper_ref) == canonical_ref
});
if is_cjs_wrapper || order_live_symbols.contains(&canonical_ref) {
captured.insert(CompactStr::new(canonical_ref.name(&link_output.symbol_db)));
}
}
}
captured
}
fn rename_shadowing_symbols_in_nested_scopes<'a>(
chunk: &Chunk,
link_output: &'a LinkStageOutput,
output_format: OutputFormat,
renamer: &mut Renamer<'a>,
) {
for module_idx in chunk.modules.iter().copied().rev() {
let Some(module) = link_output.module_table[module_idx].as_normal() else {
continue;
};
let Some(db) = &link_output.symbol_db[module_idx] else {
continue;
};
let mut ctx = NestedScopeRenamer {
module_idx,
module,
db,
scoping: db.ast_scopes.scoping(),
link_output,
renamer,
};
ctx.rename_bindings_shadowing_star_imports();
ctx.rename_bindings_shadowing_named_imports();
ctx.rename_bindings_shadowing_wrapper_params(matches!(
output_format,
OutputFormat::Iife | OutputFormat::Umd | OutputFormat::Cjs
));
ctx.rename_cjs_locals_shadowing_referenced_chunk_bindings();
}
}