diff --git a/Cargo.lock b/Cargo.lock index c67658cd8e130..fd5a1d283dbd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1747,6 +1747,7 @@ dependencies = [ "oxc_index", "oxc_semantic", "oxc_span", + "rustc-hash", ] [[package]] diff --git a/crates/oxc_mangler/Cargo.toml b/crates/oxc_mangler/Cargo.toml index 6119fd9aa6bcd..1277ad0ca5531 100644 --- a/crates/oxc_mangler/Cargo.toml +++ b/crates/oxc_mangler/Cargo.toml @@ -26,3 +26,4 @@ oxc_ast = { workspace = true } oxc_index = { workspace = true } oxc_semantic = { workspace = true } oxc_span = { workspace = true } +rustc-hash = { workspace = true } diff --git a/crates/oxc_mangler/src/lib.rs b/crates/oxc_mangler/src/lib.rs index 61598118ca8ee..bc1da3b04cc3c 100644 --- a/crates/oxc_mangler/src/lib.rs +++ b/crates/oxc_mangler/src/lib.rs @@ -1,8 +1,9 @@ use itertools::Itertools; -use oxc_ast::ast::Program; +use oxc_ast::ast::{Declaration, Program, Statement}; use oxc_index::{index_vec, Idx, IndexVec}; use oxc_semantic::{ReferenceId, ScopeTree, SemanticBuilder, SymbolId, SymbolTable}; use oxc_span::CompactStr; +use rustc_hash::FxHashSet; type Slot = usize; @@ -85,6 +86,12 @@ impl Mangler { pub fn build<'a>(mut self, program: &'a Program<'a>) -> Mangler { let semantic = SemanticBuilder::new().build(program).semantic; + let (exported_names, exported_symbols) = if self.options.top_level { + Mangler::collect_exported_symbols(program) + } else { + Default::default() + }; + // Mangle the symbol table by computing slots from the scope tree. // A slot is the occurrence index of a binding identifier inside a scope. let (mut symbol_table, scope_tree) = semantic.into_symbol_table_and_scope_tree(); @@ -126,8 +133,13 @@ impl Mangler { } } - let frequencies = - self.tally_slot_frequencies(&symbol_table, &scope_tree, total_number_of_slots, &slots); + let frequencies = self.tally_slot_frequencies( + &symbol_table, + &exported_symbols, + &scope_tree, + total_number_of_slots, + &slots, + ); let root_unresolved_references = scope_tree.root_unresolved_references(); let root_bindings = scope_tree.get_bindings(scope_tree.root_scope_id()); @@ -145,7 +157,8 @@ impl Mangler { if !is_keyword(n) && !is_special_name(n) && !root_unresolved_references.contains_key(n) - && (self.options.top_level || !root_bindings.contains_key(n)) + && !(root_bindings.contains_key(n) + && (!self.options.top_level || exported_names.contains(n))) { break name; } @@ -206,6 +219,7 @@ impl Mangler { fn tally_slot_frequencies( &self, symbol_table: &SymbolTable, + exported_symbols: &FxHashSet, scope_tree: &ScopeTree, total_number_of_slots: usize, slots: &IndexVec, @@ -213,7 +227,9 @@ impl Mangler { let root_scope_id = scope_tree.root_scope_id(); let mut frequencies = vec![SlotFrequency::default(); total_number_of_slots]; for (symbol_id, slot) in slots.iter_enumerated() { - if !self.options.top_level && symbol_table.get_scope_id(symbol_id) == root_scope_id { + if symbol_table.get_scope_id(symbol_id) == root_scope_id + && (!self.options.top_level || exported_symbols.contains(&symbol_id)) + { continue; } if is_special_name(symbol_table.get_name(symbol_id)) { @@ -228,6 +244,29 @@ impl Mangler { frequencies.sort_unstable_by_key(|x| std::cmp::Reverse(x.frequency)); frequencies } + + fn collect_exported_symbols(program: &Program) -> (FxHashSet, FxHashSet) { + program + .body + .iter() + .filter_map(|statement| { + let Statement::ExportNamedDeclaration(v) = statement else { return None }; + v.declaration.as_ref() + }) + .flat_map(|decl| { + if let Declaration::VariableDeclaration(decl) = decl { + itertools::Either::Left( + decl.declarations + .iter() + .filter_map(|decl| decl.id.get_binding_identifier()), + ) + } else { + itertools::Either::Right(decl.id().into_iter()) + } + }) + .map(|id| (id.name.to_compact_str(), id.symbol_id())) + .collect() + } } fn is_special_name(name: &str) -> bool { diff --git a/crates/oxc_minifier/tests/mangler/mod.rs b/crates/oxc_minifier/tests/mangler/mod.rs index e8a1001d42d76..3d76bed459fca 100644 --- a/crates/oxc_minifier/tests/mangler/mod.rs +++ b/crates/oxc_minifier/tests/mangler/mod.rs @@ -26,7 +26,13 @@ fn mangler() { "import { x } from 's'; export { x }", "function _ (exports) { Object.defineProperty(exports, '__esModule', { value: true }) }", ]; - let top_level_cases = ["function foo(a) {a}"]; + let top_level_cases = [ + "function foo(a) {a}", + "export function foo() {}; foo()", + "export default function foo() {}; foo()", + "export const foo = 1; foo", + "const foo = 1; foo; export { foo }", + ]; let mut snapshot = String::new(); cases.into_iter().fold(&mut snapshot, |w, case| { diff --git a/crates/oxc_minifier/tests/mangler/snapshots/mangler.snap b/crates/oxc_minifier/tests/mangler/snapshots/mangler.snap index fd36999045d8d..39756b92e6a0c 100644 --- a/crates/oxc_minifier/tests/mangler/snapshots/mangler.snap +++ b/crates/oxc_minifier/tests/mangler/snapshots/mangler.snap @@ -35,3 +35,22 @@ function foo(a) {a} function a(b) { b; } + +export function foo() {}; foo() +export function foo() {} +; +foo(); + +export default function foo() {}; foo() +export default function a() {} +; +a(); + +export const foo = 1; foo +export const foo = 1; +foo; + +const foo = 1; foo; export { foo } +const a = 1; +a; +export { a as foo };