@@ -3,24 +3,75 @@ use oxc_ast::{AstKind, ast::*};
3
3
use oxc_semantic:: { AstNode , AstNodes , ReferenceId , Scoping , SymbolId } ;
4
4
use rustc_hash:: FxHashSet ;
5
5
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) ;
9
51
collector. collect ( )
10
52
}
11
53
12
54
/// Collects symbols that are used to set `name` properties of functions and classes.
13
55
struct NameSymbolCollector < ' a , ' b > {
56
+ options : MangleOptionsKeepNames ,
14
57
scoping : & ' b Scoping ,
15
58
ast_nodes : & ' b AstNodes < ' a > ,
16
59
}
17
60
18
61
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 }
21
68
}
22
69
23
70
fn collect ( self ) -> FxHashSet < SymbolId > {
71
+ if !self . options . function && !self . options . class {
72
+ return FxHashSet :: default ( ) ;
73
+ }
74
+
24
75
self . scoping
25
76
. symbol_ids ( )
26
77
. filter ( |symbol_id| {
@@ -42,9 +93,12 @@ impl<'a, 'b: 'a> NameSymbolCollector<'a, 'b> {
42
93
fn is_name_set_declare_node ( & self , node : & ' a AstNode , symbol_id : SymbolId ) -> bool {
43
94
match node. kind ( ) {
44
95
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)
46
101
}
47
- AstKind :: Class ( cls) => cls. id . as_ref ( ) . is_some_and ( |id| id. symbol_id ( ) == symbol_id) ,
48
102
AstKind :: VariableDeclarator ( decl) => {
49
103
if let BindingPatternKind :: BindingIdentifier ( id) = & decl. id . kind {
50
104
if id. symbol_id ( ) == symbol_id {
@@ -176,9 +230,18 @@ impl<'a, 'b: 'a> NameSymbolCollector<'a, 'b> {
176
230
}
177
231
}
178
232
179
- #[ expect( clippy:: unused_self) ]
180
233
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)
182
245
}
183
246
}
184
247
@@ -191,17 +254,17 @@ mod test {
191
254
use rustc_hash:: FxHashSet ;
192
255
use std:: iter:: once;
193
256
194
- use super :: collect_name_symbols;
257
+ use super :: { MangleOptionsKeepNames , collect_name_symbols} ;
195
258
196
- fn collect ( source_text : & str ) -> FxHashSet < String > {
259
+ fn collect ( opts : MangleOptionsKeepNames , source_text : & str ) -> FxHashSet < String > {
197
260
let allocator = Allocator :: default ( ) ;
198
261
let ret = Parser :: new ( & allocator, source_text, SourceType :: mjs ( ) ) . parse ( ) ;
199
262
assert ! ( !ret. panicked, "{source_text}" ) ;
200
263
assert ! ( ret. errors. is_empty( ) , "{source_text}" ) ;
201
264
let ret = SemanticBuilder :: new ( ) . build ( & ret. program ) ;
202
265
assert ! ( ret. errors. is_empty( ) , "{source_text}" ) ;
203
266
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 ( ) ) ;
205
268
symbols
206
269
. into_iter ( )
207
270
. map ( |symbol_id| semantic. scoping ( ) . symbol_name ( symbol_id) . to_string ( ) )
@@ -210,60 +273,120 @@ mod test {
210
273
211
274
#[ test]
212
275
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
+ ) ;
215
284
}
216
285
217
286
#[ test]
218
287
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
+ ) ;
222
300
}
223
301
224
302
#[ test]
225
303
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
+ ) ;
229
316
230
- assert_eq ! ( collect( "var foo; foo ||= function() {}" ) , once( "foo" . to_string( ) ) . collect( ) ) ;
231
317
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() {}" ) ,
233
327
once( "foo" . to_string( ) ) . collect( )
234
328
) ;
235
- assert_eq ! ( collect( "var foo; foo ??= function() {}" ) , once( "foo" . to_string( ) ) . collect( ) ) ;
236
329
}
237
330
238
331
#[ test]
239
332
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
+ ) ;
244
349
}
245
350
246
351
#[ test]
247
352
fn test_default_assign ( ) {
248
353
assert_eq ! (
249
- collect( "var foo; [foo = function() {}] = []" ) ,
354
+ collect( MangleOptionsKeepNames :: function_only ( ) , "var foo; [foo = function() {}] = []" ) ,
250
355
once( "foo" . to_string( ) ) . collect( )
251
356
) ;
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( ) ) ;
254
357
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
+ ) ,
256
370
once( "foo" . to_string( ) ) . collect( )
257
371
) ;
258
372
}
259
373
260
374
#[ test]
261
375
fn test_for_in_declaration ( ) {
262
376
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 []) {}" ) ,
264
385
once( "foo" . to_string( ) ) . collect( )
265
386
) ;
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
+ ) ;
268
391
}
269
392
}
0 commit comments