use oxc::span::CompactStr;
use crate::{stages::link_stage::LinkStageOutput, utils::renamer::Renamer};
use arcstr::ArcStr;
use rolldown_common::{
Chunk, ChunkIdx, ChunkKind, GetLocalDb, OutputFormat, TaggedSymbolRef, WrapKind,
};
use rolldown_utils::ecmascript::legitimize_identifier_name;
use rustc_hash::{FxHashMap, FxHashSet};
#[tracing::instrument(level = "trace", skip_all)]
pub fn deconflict_chunk_symbols(
chunk: &mut Chunk,
link_output: &LinkStageOutput,
format: OutputFormat,
index_chunk_id_to_name: &FxHashMap<ChunkIdx, ArcStr>,
) {
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.entry_module_idx() {
Some(module) => {
let entry_module =
link_output.module_table[module].as_normal().expect("should be normal module");
link_output.metas[entry_module.idx].star_exports_from_external_modules.iter().for_each(
|rec_idx| {
let rec = &entry_module.ecma_view.import_records[*rec_idx];
let external_module = &link_output.module_table[rec.resolved_module]
.as_external()
.expect("Should be external module here");
renamer.add_symbol_in_root_scope(external_module.namespace_ref, true);
},
);
}
None => {}
}
}
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_symbol_refs.contains(&symbol_ref) {
renamer.add_symbol_in_root_scope(symbol_ref, true);
}
});
for symbol_id in db.ast_scopes.facade_symbol_classic_data().keys() {
let symbol_ref = (*module, *symbol_id).into();
if link_output.used_symbol_refs.contains(&symbol_ref) {
renamer.add_symbol_in_root_scope(symbol_ref, true);
}
}
});
}
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);
module
.stmt_infos
.iter_enumerated()
.filter(|(idx, _)| meta.stmt_info_included[*idx])
.for_each(|(_, stmt_info)| {
for declared_symbol in stmt_info
.declared_symbols
.iter()
.filter(|item| matches!(item, TaggedSymbolRef::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 {
link_output.symbol_db.is_facade_symbol(canonical_ref)
|| stmt_info.import_records.iter().any(|import_rec_idx| {
link_output.module_table[module.import_records[*import_rec_idx].resolved_module]
.is_external()
})
} else {
true
};
renamer.add_symbol_in_root_scope(symbol_ref, needs_deconflict);
}
});
});
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.exports_to_other_chunks.keys().for_each(|export_ref| {
renamer.add_symbol_in_root_scope(*export_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]
))),
)
})
.collect();
for module_idx in chunk.modules.iter().copied() {
let Some(db) = &link_output.symbol_db[module_idx] else {
continue;
};
let scoping = db.ast_scopes.scoping();
let is_cjs_wrapped = matches!(link_output.metas[module_idx].wrap_kind(), WrapKind::Cjs);
let mut iter_bindings = scoping.iter_bindings();
let Some((_, top_level_bindings)) = iter_bindings.next() else {
continue;
};
let top_level_canonical_names: FxHashSet<CompactStr> = top_level_bindings
.iter()
.filter_map(|(_, symbol_id)| {
if scoping.get_resolved_reference_ids(*symbol_id).is_empty() {
return None;
}
let symbol_ref = (module_idx, *symbol_id).into();
renamer.get_canonical_name(symbol_ref).cloned()
})
.collect();
for (_, bindings) in iter_bindings {
for (&name, symbol_id) in bindings {
if is_cjs_wrapped || top_level_canonical_names.contains(name) {
let symbol_ref = (module_idx, *symbol_id).into();
renamer.register_nested_scope_symbols(symbol_ref, name, is_cjs_wrapped);
}
}
}
}
chunk.canonical_names = renamer.into_canonical_names();
}