Skip to content

Commit d32678a

Browse files
committed
feat(mangler): support keep_names option
1 parent 95f70a0 commit d32678a

File tree

8 files changed

+301
-52
lines changed

8 files changed

+301
-52
lines changed

crates/oxc_mangler/src/keep_names.rs

Lines changed: 157 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,75 @@ use oxc_ast::{AstKind, ast::*};
33
use oxc_semantic::{AstNode, AstNodes, ReferenceId, Scoping, SymbolId};
44
use rustc_hash::FxHashSet;
55

6-
#[cfg_attr(not(test), expect(dead_code))]
7-
pub fn collect_name_symbols(scoping: &Scoping, ast_nodes: &AstNodes) -> FxHashSet<SymbolId> {
8-
let collector = NameSymbolCollector::new(scoping, ast_nodes);
6+
#[derive(Debug, Clone, Copy, Default)]
7+
pub struct MangleOptionsKeepNames {
8+
/// Keep function names so that `Function.prototype.name` is preserved.
9+
///
10+
/// Default `false`
11+
pub function: bool,
12+
13+
/// Keep class names so that `Class.prototype.name` is preserved.
14+
///
15+
/// Default `false`
16+
pub class: bool,
17+
}
18+
19+
impl MangleOptionsKeepNames {
20+
pub fn all_false() -> Self {
21+
Self { function: false, class: false }
22+
}
23+
24+
pub fn all_true() -> Self {
25+
Self { function: true, class: true }
26+
}
27+
28+
#[cfg(test)]
29+
pub(crate) fn function_only() -> Self {
30+
Self { function: true, class: false }
31+
}
32+
33+
#[cfg(test)]
34+
pub(crate) fn class_only() -> Self {
35+
Self { function: false, class: true }
36+
}
37+
}
38+
39+
impl From<bool> for MangleOptionsKeepNames {
40+
fn from(keep_names: bool) -> Self {
41+
if keep_names { Self::all_true() } else { Self::all_false() }
42+
}
43+
}
44+
45+
pub fn collect_name_symbols(
46+
options: MangleOptionsKeepNames,
47+
scoping: &Scoping,
48+
ast_nodes: &AstNodes,
49+
) -> FxHashSet<SymbolId> {
50+
let collector = NameSymbolCollector::new(options, scoping, ast_nodes);
951
collector.collect()
1052
}
1153

1254
/// Collects symbols that are used to set `name` properties of functions and classes.
1355
struct NameSymbolCollector<'a, 'b> {
56+
options: MangleOptionsKeepNames,
1457
scoping: &'b Scoping,
1558
ast_nodes: &'b AstNodes<'a>,
1659
}
1760

1861
impl<'a, 'b: 'a> NameSymbolCollector<'a, 'b> {
19-
fn new(scoping: &'b Scoping, ast_nodes: &'b AstNodes<'a>) -> Self {
20-
Self { scoping, ast_nodes }
62+
fn new(
63+
options: MangleOptionsKeepNames,
64+
scoping: &'b Scoping,
65+
ast_nodes: &'b AstNodes<'a>,
66+
) -> Self {
67+
Self { options, scoping, ast_nodes }
2168
}
2269

2370
fn collect(self) -> FxHashSet<SymbolId> {
71+
if !self.options.function && !self.options.class {
72+
return FxHashSet::default();
73+
}
74+
2475
self.scoping
2576
.symbol_ids()
2677
.filter(|symbol_id| {
@@ -42,9 +93,12 @@ impl<'a, 'b: 'a> NameSymbolCollector<'a, 'b> {
4293
fn is_name_set_declare_node(&self, node: &'a AstNode, symbol_id: SymbolId) -> bool {
4394
match node.kind() {
4495
AstKind::Function(function) => {
45-
function.id.as_ref().is_some_and(|id| id.symbol_id() == symbol_id)
96+
self.options.function
97+
&& function.id.as_ref().is_some_and(|id| id.symbol_id() == symbol_id)
98+
}
99+
AstKind::Class(cls) => {
100+
self.options.class && cls.id.as_ref().is_some_and(|id| id.symbol_id() == symbol_id)
46101
}
47-
AstKind::Class(cls) => cls.id.as_ref().is_some_and(|id| id.symbol_id() == symbol_id),
48102
AstKind::VariableDeclarator(decl) => {
49103
if let BindingPatternKind::BindingIdentifier(id) = &decl.id.kind {
50104
if id.symbol_id() == symbol_id {
@@ -176,9 +230,18 @@ impl<'a, 'b: 'a> NameSymbolCollector<'a, 'b> {
176230
}
177231
}
178232

179-
#[expect(clippy::unused_self)]
180233
fn is_expression_whose_name_needs_to_be_kept(&self, expr: &Expression) -> bool {
181-
expr.is_anonymous_function_definition()
234+
let is_anonymous = expr.is_anonymous_function_definition();
235+
if !is_anonymous {
236+
return false;
237+
}
238+
239+
if self.options.class && self.options.function {
240+
return true;
241+
}
242+
243+
let is_class = matches!(expr, Expression::ClassExpression(_));
244+
(self.options.class && is_class) || (self.options.function && !is_class)
182245
}
183246
}
184247

@@ -191,17 +254,17 @@ mod test {
191254
use rustc_hash::FxHashSet;
192255
use std::iter::once;
193256

194-
use super::collect_name_symbols;
257+
use super::{MangleOptionsKeepNames, collect_name_symbols};
195258

196-
fn collect(source_text: &str) -> FxHashSet<String> {
259+
fn collect(opts: MangleOptionsKeepNames, source_text: &str) -> FxHashSet<String> {
197260
let allocator = Allocator::default();
198261
let ret = Parser::new(&allocator, source_text, SourceType::mjs()).parse();
199262
assert!(!ret.panicked, "{source_text}");
200263
assert!(ret.errors.is_empty(), "{source_text}");
201264
let ret = SemanticBuilder::new().build(&ret.program);
202265
assert!(ret.errors.is_empty(), "{source_text}");
203266
let semantic = ret.semantic;
204-
let symbols = collect_name_symbols(semantic.scoping(), semantic.nodes());
267+
let symbols = collect_name_symbols(opts, semantic.scoping(), semantic.nodes());
205268
symbols
206269
.into_iter()
207270
.map(|symbol_id| semantic.scoping().symbol_name(symbol_id).to_string())
@@ -210,60 +273,120 @@ mod test {
210273

211274
#[test]
212275
fn test_declarations() {
213-
assert_eq!(collect("function foo() {}"), once("foo".to_string()).collect());
214-
assert_eq!(collect("class Foo {}"), once("Foo".to_string()).collect());
276+
assert_eq!(
277+
collect(MangleOptionsKeepNames::function_only(), "function foo() {}"),
278+
once("foo".to_string()).collect()
279+
);
280+
assert_eq!(
281+
collect(MangleOptionsKeepNames::class_only(), "class Foo {}"),
282+
once("Foo".to_string()).collect()
283+
);
215284
}
216285

217286
#[test]
218287
fn test_simple_declare_init() {
219-
assert_eq!(collect("var foo = function() {}"), once("foo".to_string()).collect());
220-
assert_eq!(collect("var foo = () => {}"), once("foo".to_string()).collect());
221-
assert_eq!(collect("var Foo = class {}"), once("Foo".to_string()).collect());
288+
assert_eq!(
289+
collect(MangleOptionsKeepNames::function_only(), "var foo = function() {}"),
290+
once("foo".to_string()).collect()
291+
);
292+
assert_eq!(
293+
collect(MangleOptionsKeepNames::function_only(), "var foo = () => {}"),
294+
once("foo".to_string()).collect()
295+
);
296+
assert_eq!(
297+
collect(MangleOptionsKeepNames::class_only(), "var Foo = class {}"),
298+
once("Foo".to_string()).collect()
299+
);
222300
}
223301

224302
#[test]
225303
fn test_simple_assign() {
226-
assert_eq!(collect("var foo; foo = function() {}"), once("foo".to_string()).collect());
227-
assert_eq!(collect("var foo; foo = () => {}"), once("foo".to_string()).collect());
228-
assert_eq!(collect("var Foo; Foo = class {}"), once("Foo".to_string()).collect());
304+
assert_eq!(
305+
collect(MangleOptionsKeepNames::function_only(), "var foo; foo = function() {}"),
306+
once("foo".to_string()).collect()
307+
);
308+
assert_eq!(
309+
collect(MangleOptionsKeepNames::function_only(), "var foo; foo = () => {}"),
310+
once("foo".to_string()).collect()
311+
);
312+
assert_eq!(
313+
collect(MangleOptionsKeepNames::class_only(), "var Foo; Foo = class {}"),
314+
once("Foo".to_string()).collect()
315+
);
229316

230-
assert_eq!(collect("var foo; foo ||= function() {}"), once("foo".to_string()).collect());
231317
assert_eq!(
232-
collect("var foo = 1; foo &&= function() {}"),
318+
collect(MangleOptionsKeepNames::function_only(), "var foo; foo ||= function() {}"),
319+
once("foo".to_string()).collect()
320+
);
321+
assert_eq!(
322+
collect(MangleOptionsKeepNames::function_only(), "var foo = 1; foo &&= function() {}"),
323+
once("foo".to_string()).collect()
324+
);
325+
assert_eq!(
326+
collect(MangleOptionsKeepNames::function_only(), "var foo; foo ??= function() {}"),
233327
once("foo".to_string()).collect()
234328
);
235-
assert_eq!(collect("var foo; foo ??= function() {}"), once("foo".to_string()).collect());
236329
}
237330

238331
#[test]
239332
fn test_default_declarations() {
240-
assert_eq!(collect("var [foo = function() {}] = []"), once("foo".to_string()).collect());
241-
assert_eq!(collect("var [foo = () => {}] = []"), once("foo".to_string()).collect());
242-
assert_eq!(collect("var [Foo = class {}] = []"), once("Foo".to_string()).collect());
243-
assert_eq!(collect("var { foo = function() {} } = {}"), once("foo".to_string()).collect());
333+
assert_eq!(
334+
collect(MangleOptionsKeepNames::function_only(), "var [foo = function() {}] = []"),
335+
once("foo".to_string()).collect()
336+
);
337+
assert_eq!(
338+
collect(MangleOptionsKeepNames::function_only(), "var [foo = () => {}] = []"),
339+
once("foo".to_string()).collect()
340+
);
341+
assert_eq!(
342+
collect(MangleOptionsKeepNames::class_only(), "var [Foo = class {}] = []"),
343+
once("Foo".to_string()).collect()
344+
);
345+
assert_eq!(
346+
collect(MangleOptionsKeepNames::function_only(), "var { foo = function() {} } = {}"),
347+
once("foo".to_string()).collect()
348+
);
244349
}
245350

246351
#[test]
247352
fn test_default_assign() {
248353
assert_eq!(
249-
collect("var foo; [foo = function() {}] = []"),
354+
collect(MangleOptionsKeepNames::function_only(), "var foo; [foo = function() {}] = []"),
250355
once("foo".to_string()).collect()
251356
);
252-
assert_eq!(collect("var foo; [foo = () => {}] = []"), once("foo".to_string()).collect());
253-
assert_eq!(collect("var Foo; [Foo = class {}] = []"), once("Foo".to_string()).collect());
254357
assert_eq!(
255-
collect("var foo; ({ foo = function() {} } = {})"),
358+
collect(MangleOptionsKeepNames::function_only(), "var foo; [foo = () => {}] = []"),
359+
once("foo".to_string()).collect()
360+
);
361+
assert_eq!(
362+
collect(MangleOptionsKeepNames::class_only(), "var Foo; [Foo = class {}] = []"),
363+
once("Foo".to_string()).collect()
364+
);
365+
assert_eq!(
366+
collect(
367+
MangleOptionsKeepNames::function_only(),
368+
"var foo; ({ foo = function() {} } = {})"
369+
),
256370
once("foo".to_string()).collect()
257371
);
258372
}
259373

260374
#[test]
261375
fn test_for_in_declaration() {
262376
assert_eq!(
263-
collect("for (var foo = function() {} in []) {}"),
377+
collect(
378+
MangleOptionsKeepNames::function_only(),
379+
"for (var foo = function() {} in []) {}"
380+
),
381+
once("foo".to_string()).collect()
382+
);
383+
assert_eq!(
384+
collect(MangleOptionsKeepNames::function_only(), "for (var foo = () => {} in []) {}"),
264385
once("foo".to_string()).collect()
265386
);
266-
assert_eq!(collect("for (var foo = () => {} in []) {}"), once("foo".to_string()).collect());
267-
assert_eq!(collect("for (var Foo = class {} in []) {}"), once("Foo".to_string()).collect());
387+
assert_eq!(
388+
collect(MangleOptionsKeepNames::class_only(), "for (var Foo = class {} in []) {}"),
389+
once("Foo".to_string()).collect()
390+
);
268391
}
269392
}

0 commit comments

Comments
 (0)