use std::cell::OnceCell;
use std::collections::hash_map::Entry;
use rustc_hash::FxHashMap;
use oxc::semantic::{ScopeId, Scoping, SymbolId};
use oxc::syntax::keyword::{GLOBAL_OBJECTS, RESERVED_KEYWORDS};
use oxc_str::{CompactStr, Ident, IdentHashSet};
use rolldown_common::{
EcmaModuleAstUsage, ModuleIdx, NormalModule, OutputFormat, StmtInfoMeta, SymbolRef, SymbolRefDb,
SymbolRefDbForModule, WrapKind,
};
use rolldown_utils::concat_string;
use crate::stages::link_stage::LinkStageOutput;
use crate::utils::chunk::conflict_resolver::ConflictResolver;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RootBindingKind {
Authored,
Synthesized,
}
pub struct InnerBindingNames<'a> {
scopings: Vec<(&'a Scoping, bool)>,
names: OnceCell<IdentHashSet<'a>>,
}
impl<'a> InnerBindingNames<'a> {
pub fn new(scopings: Vec<(&'a Scoping, bool)>) -> Self {
Self { scopings, names: OnceCell::new() }
}
fn contains(&self, name: &str) -> bool {
self
.names
.get_or_init(|| {
let mut names = IdentHashSet::default();
for (scoping, root_is_inner) in &self.scopings {
let root_scope_id = scoping.root_scope_id();
for symbol_id in scoping.symbol_ids() {
let name = scoping.symbol_ident(symbol_id);
if scoping.symbol_scope_id(symbol_id) == root_scope_id
&& (!root_is_inner || scoping.get_binding(root_scope_id, name) != Some(symbol_id))
{
continue;
}
names.insert(name);
}
}
names
})
.contains(&Ident::from(name))
}
}
impl std::fmt::Debug for InnerBindingNames<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("InnerBindingNames").finish_non_exhaustive()
}
}
#[derive(Debug)]
pub struct Renamer<'name> {
resolver: ConflictResolver,
canonical_names: FxHashMap<SymbolRef, CompactStr>,
symbol_db: &'name SymbolRefDb,
inner_binding_names: InnerBindingNames<'name>,
}
impl<'name> Renamer<'name> {
pub fn new(
symbol_db: &'name SymbolRefDb,
format: OutputFormat,
inner_binding_names: InnerBindingNames<'name>,
) -> Self {
let mut manual_reserved = match format {
OutputFormat::Esm => vec![],
OutputFormat::Cjs => {
vec!["module", "require", "__filename", "__dirname", "exports", "Symbol"]
}
OutputFormat::Iife | OutputFormat::Umd => vec!["exports", "Symbol"], };
manual_reserved.extend(["Object", "Promise"]);
Self {
canonical_names: FxHashMap::default(),
symbol_db,
resolver: ConflictResolver::new(
manual_reserved
.iter()
.chain(RESERVED_KEYWORDS.iter())
.chain(GLOBAL_OBJECTS.iter())
.map(|s| CompactStr::new(s)),
),
inner_binding_names,
}
}
pub fn get_canonical_name(&self, symbol_ref: SymbolRef) -> Option<&CompactStr> {
let canonical_ref = self.symbol_db.canonical_ref_for(symbol_ref);
self.canonical_names.get(&canonical_ref)
}
pub fn printed_name(&self, symbol_ref: SymbolRef) -> Option<&CompactStr> {
self.get_canonical_name(self.symbol_db.canonical_ref_resolving_namespace(symbol_ref))
}
pub fn reserve(&mut self, name: CompactStr) {
self.resolver.reserve(name);
}
#[inline]
fn is_name_available_with(
symbol_db: &SymbolRefDb,
candidate_name: &str,
symbol_ref: SymbolRef,
is_original_name: bool,
) -> bool {
if !is_original_name && has_binding(symbol_db, symbol_ref.owner, candidate_name) {
return false;
}
true
}
pub fn add_symbol_in_root_scope(&mut self, symbol_ref: SymbolRef, kind: RootBindingKind) {
let canonical_ref = symbol_ref.canonical_ref(self.symbol_db);
let Entry::Vacant(slot) = self.canonical_names.entry(canonical_ref) else {
return;
};
let original_name = self.symbol_db.original_name(canonical_ref);
let symbol_db = self.symbol_db;
let inner_binding_names = &self.inner_binding_names;
let resolved = self.resolver.resolve(original_name, |candidate, is_original| match kind {
RootBindingKind::Authored => {
Self::is_name_available_with(symbol_db, candidate, canonical_ref, is_original)
}
RootBindingKind::Synthesized => !inner_binding_names.contains(candidate),
});
slot.insert(resolved);
}
pub fn create_conflictless_name(&mut self, hint: &str) -> CompactStr {
let inner_binding_names = &self.inner_binding_names;
self
.resolver
.resolve(CompactStr::new(hint), |candidate, _| !inner_binding_names.contains(candidate))
}
pub fn rename_inner_binding(&mut self, symbol_ref: SymbolRef, original_name: &str) {
let canonical_ref = symbol_ref.canonical_ref(self.symbol_db);
if canonical_ref != symbol_ref || self.canonical_names.contains_key(&canonical_ref) {
return;
}
for count in 1u32.. {
let name: CompactStr =
concat_string!(original_name, "$", itoa::Buffer::new().format(count)).into();
if self.resolver.contains(&name) {
continue;
}
if has_binding(self.symbol_db, symbol_ref.owner, &name) {
self.resolver.reserve(name);
continue;
}
self.resolver.reserve(name.clone());
self.canonical_names.insert(symbol_ref, name);
return;
}
}
#[inline]
pub fn into_canonical_names(self) -> FxHashMap<SymbolRef, CompactStr> {
self.canonical_names
}
}
fn has_binding(symbol_db: &SymbolRefDb, module_idx: ModuleIdx, name: &str) -> bool {
let Some(db) = &symbol_db[module_idx] else {
return false;
};
db.ast_scopes.scoping().iter_bindings().any(|(_, bindings)| bindings.contains_key(name))
}
pub fn fixed_names_in_module_body(
module: &NormalModule,
link_output: &LinkStageOutput,
output_format: OutputFormat,
) -> Vec<&'static str> {
let stmt_infos = &link_output.stmt_infos[module.idx];
let mut names = vec![];
if module.import_records.iter().any(|rec| rec.kind.is_dynamic())
|| stmt_infos
.iter()
.any(|stmt_info| stmt_info.meta.contains(StmtInfoMeta::NonStaticDynamicImport))
{
names.extend(["Promise", "Object"]);
}
if !module.ecma_view.rolldown_file_url_references.is_empty() {
names.push("URL");
}
if matches!(output_format, OutputFormat::Cjs) {
names.extend(["require", "__filename", "__dirname"]);
}
names
}
pub fn cjs_wrapper_fixed_params(module: &NormalModule) -> &'static [&'static str] {
if module.cjs_this_ref.is_some() || module.ast_usage.contains(EcmaModuleAstUsage::ModuleRef) {
&["exports", "module"]
} else if module.ast_usage.intersects(EcmaModuleAstUsage::ModuleOrExports) {
&["exports"]
} else {
&[]
}
}
pub struct NestedScopeRenamer<'a, 'r> {
pub module_idx: ModuleIdx,
pub module: &'a NormalModule,
pub db: &'a SymbolRefDbForModule,
pub scoping: &'a Scoping,
pub link_output: &'a LinkStageOutput,
pub renamer: &'r mut Renamer<'a>,
}
impl NestedScopeRenamer<'_, '_> {
fn root_scope_is_inner(&self) -> bool {
matches!(self.link_output.metas[self.module_idx].wrap_kind(), WrapKind::Cjs)
}
fn rename_bindings_on_path(
&mut self,
reference_scope: ScopeId,
name: Ident<'_>,
target: SymbolId,
) {
let scoping = self.scoping;
let root_scope_id = scoping.root_scope_id();
let root_scope_is_inner = self.root_scope_is_inner();
for scope_id in scoping.scope_ancestors(reference_scope) {
if scope_id == root_scope_id && !root_scope_is_inner {
break;
}
if let Some(binding) = scoping.get_binding(scope_id, name)
&& binding != target
{
self
.renamer
.rename_inner_binding((self.module_idx, binding).into(), scoping.symbol_name(binding));
}
}
}
pub fn rename_bindings_shadowing_star_imports(&mut self) {
for member_expr_ref in
self.link_output.metas[self.module_idx].resolved_member_expr_refs.values()
{
let Some(reference_id) = member_expr_ref.reference_id else {
continue;
};
let current_reference = self.scoping.get_reference(reference_id);
let Some(symbol) = current_reference.symbol_id() else {
continue;
};
let Some(resolved_symbol) = member_expr_ref.resolved else {
continue;
};
let Some(printed_name) = self.renamer.printed_name(resolved_symbol).cloned() else {
continue;
};
self.rename_bindings_on_path(
current_reference.scope_id(),
Ident::from(printed_name.as_str()),
symbol,
);
}
}
pub fn rename_bindings_shadowing_named_imports(&mut self) {
for (symbol_ref, _named_import) in &self.module.named_imports {
if self.db.is_facade_symbol(symbol_ref.symbol) {
continue;
}
let Some(printed_name) = self.renamer.printed_name(*symbol_ref).cloned() else {
continue;
};
let printed_name = Ident::from(printed_name.as_str());
let scoping = self.scoping;
for reference in scoping.get_resolved_references(symbol_ref.symbol) {
self.rename_bindings_on_path(reference.scope_id(), printed_name, symbol_ref.symbol);
}
}
}
pub fn rename_bindings_shadowing_fixed_names(&mut self, output_format: OutputFormat) {
let fixed_names = fixed_names_in_module_body(self.module, self.link_output, output_format);
if fixed_names.is_empty() {
return;
}
let root_scope_id = self.scoping.root_scope_id();
let root_scope_is_inner = self.root_scope_is_inner();
for (scope_id, bindings) in self.scoping.iter_bindings() {
let is_root = scope_id == root_scope_id;
if is_root && !root_scope_is_inner {
continue;
}
for (&name, symbol_id) in bindings {
if !fixed_names.contains(&name.as_str()) {
continue;
}
let symbol_ref = (self.module_idx, *symbol_id).into();
self.renamer.rename_inner_binding(symbol_ref, name.as_str());
}
}
}
}