@@ -3,24 +3,75 @@ use oxc_ast::{AstKind, ast::*};
33use oxc_semantic:: { AstNode , AstNodes , ReferenceId , Scoping , SymbolId } ;
44use 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.
1355struct NameSymbolCollector < ' a , ' b > {
56+ options : MangleOptionsKeepNames ,
1457 scoping : & ' b Scoping ,
1558 ast_nodes : & ' b AstNodes < ' a > ,
1659}
1760
1861impl < ' 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