Skip to content

Commit 445a32b

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

File tree

8 files changed

+297
-52
lines changed

8 files changed

+297
-52
lines changed

crates/oxc_mangler/src/keep_names.rs

+153-34
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,68 @@ 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> {
@@ -42,9 +89,12 @@ impl<'a, 'b: 'a> NameSymbolCollector<'a, 'b> {
4289
fn is_name_set_declare_node(&self, node: &'a AstNode, symbol_id: SymbolId) -> bool {
4390
match node.kind() {
4491
AstKind::Function(function) => {
45-
function.id.as_ref().is_some_and(|id| id.symbol_id() == symbol_id)
92+
self.options.function
93+
&& function.id.as_ref().is_some_and(|id| id.symbol_id() == symbol_id)
94+
}
95+
AstKind::Class(cls) => {
96+
self.options.class && cls.id.as_ref().is_some_and(|id| id.symbol_id() == symbol_id)
4697
}
47-
AstKind::Class(cls) => cls.id.as_ref().is_some_and(|id| id.symbol_id() == symbol_id),
4898
AstKind::VariableDeclarator(decl) => {
4999
if let BindingPatternKind::BindingIdentifier(id) = &decl.id.kind {
50100
if id.symbol_id() == symbol_id {
@@ -176,9 +226,18 @@ impl<'a, 'b: 'a> NameSymbolCollector<'a, 'b> {
176226
}
177227
}
178228

179-
#[expect(clippy::unused_self)]
180229
fn is_expression_whose_name_needs_to_be_kept(&self, expr: &Expression) -> bool {
181-
expr.is_anonymous_function_definition()
230+
let is_anonymous = expr.is_anonymous_function_definition();
231+
if !is_anonymous {
232+
return false;
233+
}
234+
235+
if self.options.class && self.options.function {
236+
return true;
237+
}
238+
239+
let is_class = matches!(expr, Expression::ClassExpression(_));
240+
(self.options.class && is_class) || (self.options.function && !is_class)
182241
}
183242
}
184243

@@ -191,17 +250,17 @@ mod test {
191250
use rustc_hash::FxHashSet;
192251
use std::iter::once;
193252

194-
use super::collect_name_symbols;
253+
use super::{MangleOptionsKeepNames, collect_name_symbols};
195254

196-
fn collect(source_text: &str) -> FxHashSet<String> {
255+
fn collect(opts: MangleOptionsKeepNames, source_text: &str) -> FxHashSet<String> {
197256
let allocator = Allocator::default();
198257
let ret = Parser::new(&allocator, source_text, SourceType::mjs()).parse();
199258
assert!(!ret.panicked, "{source_text}");
200259
assert!(ret.errors.is_empty(), "{source_text}");
201260
let ret = SemanticBuilder::new().build(&ret.program);
202261
assert!(ret.errors.is_empty(), "{source_text}");
203262
let semantic = ret.semantic;
204-
let symbols = collect_name_symbols(semantic.scoping(), semantic.nodes());
263+
let symbols = collect_name_symbols(opts, semantic.scoping(), semantic.nodes());
205264
symbols
206265
.into_iter()
207266
.map(|symbol_id| semantic.scoping().symbol_name(symbol_id).to_string())
@@ -210,60 +269,120 @@ mod test {
210269

211270
#[test]
212271
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());
272+
assert_eq!(
273+
collect(MangleOptionsKeepNames::function_only(), "function foo() {}"),
274+
once("foo".to_string()).collect()
275+
);
276+
assert_eq!(
277+
collect(MangleOptionsKeepNames::class_only(), "class Foo {}"),
278+
once("Foo".to_string()).collect()
279+
);
215280
}
216281

217282
#[test]
218283
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());
284+
assert_eq!(
285+
collect(MangleOptionsKeepNames::function_only(), "var foo = function() {}"),
286+
once("foo".to_string()).collect()
287+
);
288+
assert_eq!(
289+
collect(MangleOptionsKeepNames::function_only(), "var foo = () => {}"),
290+
once("foo".to_string()).collect()
291+
);
292+
assert_eq!(
293+
collect(MangleOptionsKeepNames::class_only(), "var Foo = class {}"),
294+
once("Foo".to_string()).collect()
295+
);
222296
}
223297

224298
#[test]
225299
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());
300+
assert_eq!(
301+
collect(MangleOptionsKeepNames::function_only(), "var foo; foo = function() {}"),
302+
once("foo".to_string()).collect()
303+
);
304+
assert_eq!(
305+
collect(MangleOptionsKeepNames::function_only(), "var foo; foo = () => {}"),
306+
once("foo".to_string()).collect()
307+
);
308+
assert_eq!(
309+
collect(MangleOptionsKeepNames::class_only(), "var Foo; Foo = class {}"),
310+
once("Foo".to_string()).collect()
311+
);
229312

230-
assert_eq!(collect("var foo; foo ||= function() {}"), once("foo".to_string()).collect());
231313
assert_eq!(
232-
collect("var foo = 1; foo &&= function() {}"),
314+
collect(MangleOptionsKeepNames::function_only(), "var foo; foo ||= function() {}"),
315+
once("foo".to_string()).collect()
316+
);
317+
assert_eq!(
318+
collect(MangleOptionsKeepNames::function_only(), "var foo = 1; foo &&= function() {}"),
319+
once("foo".to_string()).collect()
320+
);
321+
assert_eq!(
322+
collect(MangleOptionsKeepNames::function_only(), "var foo; foo ??= function() {}"),
233323
once("foo".to_string()).collect()
234324
);
235-
assert_eq!(collect("var foo; foo ??= function() {}"), once("foo".to_string()).collect());
236325
}
237326

238327
#[test]
239328
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());
329+
assert_eq!(
330+
collect(MangleOptionsKeepNames::function_only(), "var [foo = function() {}] = []"),
331+
once("foo".to_string()).collect()
332+
);
333+
assert_eq!(
334+
collect(MangleOptionsKeepNames::function_only(), "var [foo = () => {}] = []"),
335+
once("foo".to_string()).collect()
336+
);
337+
assert_eq!(
338+
collect(MangleOptionsKeepNames::class_only(), "var [Foo = class {}] = []"),
339+
once("Foo".to_string()).collect()
340+
);
341+
assert_eq!(
342+
collect(MangleOptionsKeepNames::function_only(), "var { foo = function() {} } = {}"),
343+
once("foo".to_string()).collect()
344+
);
244345
}
245346

246347
#[test]
247348
fn test_default_assign() {
248349
assert_eq!(
249-
collect("var foo; [foo = function() {}] = []"),
350+
collect(MangleOptionsKeepNames::function_only(), "var foo; [foo = function() {}] = []"),
250351
once("foo".to_string()).collect()
251352
);
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());
254353
assert_eq!(
255-
collect("var foo; ({ foo = function() {} } = {})"),
354+
collect(MangleOptionsKeepNames::function_only(), "var foo; [foo = () => {}] = []"),
355+
once("foo".to_string()).collect()
356+
);
357+
assert_eq!(
358+
collect(MangleOptionsKeepNames::class_only(), "var Foo; [Foo = class {}] = []"),
359+
once("Foo".to_string()).collect()
360+
);
361+
assert_eq!(
362+
collect(
363+
MangleOptionsKeepNames::function_only(),
364+
"var foo; ({ foo = function() {} } = {})"
365+
),
256366
once("foo".to_string()).collect()
257367
);
258368
}
259369

260370
#[test]
261371
fn test_for_in_declaration() {
262372
assert_eq!(
263-
collect("for (var foo = function() {} in []) {}"),
373+
collect(
374+
MangleOptionsKeepNames::function_only(),
375+
"for (var foo = function() {} in []) {}"
376+
),
377+
once("foo".to_string()).collect()
378+
);
379+
assert_eq!(
380+
collect(MangleOptionsKeepNames::function_only(), "for (var foo = () => {} in []) {}"),
264381
once("foo".to_string()).collect()
265382
);
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());
383+
assert_eq!(
384+
collect(MangleOptionsKeepNames::class_only(), "for (var Foo = class {} in []) {}"),
385+
once("Foo".to_string()).collect()
386+
);
268387
}
269388
}

0 commit comments

Comments
 (0)