@@ -62,6 +62,7 @@ impl<'a> PeepholeReplaceKnownMethods {
62
62
"indexOf" | "lastIndexOf" => Self :: try_fold_string_index_of ( ce, name, object, ctx) ,
63
63
"charAt" => Self :: try_fold_string_char_at ( ce, object, ctx) ,
64
64
"charCodeAt" => Self :: try_fold_string_char_code_at ( ce, object, ctx) ,
65
+ "concat" => Self :: try_fold_concat ( ce, ctx) ,
65
66
"replace" | "replaceAll" => Self :: try_fold_string_replace ( ce, name, object, ctx) ,
66
67
"fromCharCode" => Self :: try_fold_string_from_char_code ( ce, object, ctx) ,
67
68
"toString" => Self :: try_fold_to_string ( ce, object, ctx) ,
@@ -420,6 +421,68 @@ impl<'a> PeepholeReplaceKnownMethods {
420
421
) ;
421
422
self . changed = true ;
422
423
}
424
+
425
+ /// `[].concat(1, 2)` -> `[1, 2]`
426
+ fn try_fold_concat (
427
+ ce : & mut CallExpression < ' a > ,
428
+ ctx : & mut TraverseCtx < ' a > ,
429
+ ) -> Option < Expression < ' a > > {
430
+ // let concat chaining reduction handle it first
431
+ if let Ancestor :: StaticMemberExpressionObject ( parent_member) = ctx. parent ( ) {
432
+ if parent_member. property ( ) . name . as_str ( ) == "concat" {
433
+ return None ;
434
+ }
435
+ }
436
+
437
+ let Expression :: StaticMemberExpression ( member) = & mut ce. callee else { unreachable ! ( ) } ;
438
+ let Expression :: ArrayExpression ( array_expr) = & mut member. object else { return None } ;
439
+
440
+ let can_merge_until = ce
441
+ . arguments
442
+ . iter ( )
443
+ . enumerate ( )
444
+ . take_while ( |( _, argument) | match argument {
445
+ Argument :: SpreadElement ( _) => false ,
446
+ match_expression ! ( Argument ) => {
447
+ let argument = argument. to_expression ( ) ;
448
+ if argument. is_literal ( ) {
449
+ true
450
+ } else {
451
+ matches ! ( argument, Expression :: ArrayExpression ( _) )
452
+ }
453
+ }
454
+ } )
455
+ . map ( |( i, _) | i)
456
+ . last ( ) ;
457
+
458
+ if let Some ( can_merge_until) = can_merge_until {
459
+ for argument in ce. arguments . drain ( ..=can_merge_until) {
460
+ let argument = argument. into_expression ( ) ;
461
+ if argument. is_literal ( ) {
462
+ array_expr. elements . push ( ArrayExpressionElement :: from ( argument) ) ;
463
+ } else {
464
+ let Expression :: ArrayExpression ( mut argument_array) = argument else {
465
+ unreachable ! ( )
466
+ } ;
467
+ array_expr. elements . append ( & mut argument_array. elements ) ;
468
+ }
469
+ }
470
+ }
471
+
472
+ if ce. arguments . is_empty ( ) {
473
+ Some ( ctx. ast . move_expression ( & mut member. object ) )
474
+ } else if can_merge_until. is_some ( ) {
475
+ Some ( ctx. ast . expression_call (
476
+ ce. span ,
477
+ ctx. ast . move_expression ( & mut ce. callee ) ,
478
+ Option :: < TSTypeParameterInstantiation > :: None ,
479
+ ctx. ast . move_vec ( & mut ce. arguments ) ,
480
+ false ,
481
+ ) )
482
+ } else {
483
+ None
484
+ }
485
+ }
423
486
}
424
487
425
488
/// Port from: <https://github.com/google/closure-compiler/blob/v20240609/test/com/google/javascript/jscomp/PeepholeReplaceKnownMethodsTest.java>
@@ -1167,52 +1230,45 @@ mod test {
1167
1230
#[ test]
1168
1231
fn test_fold_concat_chaining ( ) {
1169
1232
// array
1170
- fold ( "[1,2].concat(1).concat(2,['abc']).concat('abc')" , "[1,2].concat( 1,2,[ 'abc'] ,'abc') " ) ;
1171
- fold ( "[].concat(['abc']).concat(1).concat([2,3])" , "[].concat([ 'abc'] ,1,[ 2,3]) " ) ;
1233
+ fold ( "[1,2].concat(1).concat(2,['abc']).concat('abc')" , "[1,2, 1,2,'abc','abc'] " ) ;
1234
+ fold ( "[].concat(['abc']).concat(1).concat([2,3])" , "['abc',1,2,3]" ) ;
1172
1235
1173
1236
fold ( "var x, y; [1].concat(x).concat(y)" , "var x, y; [1].concat(x, y)" ) ;
1174
1237
fold ( "var y; [1].concat(x).concat(y)" , "var y; [1].concat(x, y)" ) ; // x might have a getter that updates y, but that side effect is preserved correctly
1175
1238
fold ( "var x; [1].concat(x.a).concat(x)" , "var x; [1].concat(x.a, x)" ) ; // x.a might have a getter that updates x, but that side effect is preserved correctly
1176
1239
1177
- fold_same ( "[].concat(1)" ) ;
1178
-
1179
1240
// string
1180
1241
fold ( "'1'.concat(1).concat(2,['abc']).concat('abc')" , "'1'.concat(1,2,['abc'],'abc')" ) ;
1181
1242
fold ( "''.concat(['abc']).concat(1).concat([2,3])" , "''.concat(['abc'],1,[2,3])" ) ;
1243
+ fold_same ( "''.concat(1)" ) ;
1182
1244
1183
1245
fold ( "var x, y; ''.concat(x).concat(y)" , "var x, y; ''.concat(x, y)" ) ;
1184
1246
fold ( "var y; ''.concat(x).concat(y)" , "var y; ''.concat(x, y)" ) ; // x might have a getter that updates y, but that side effect is preserved correctly
1185
1247
fold ( "var x; ''.concat(x.a).concat(x)" , "var x; ''.concat(x.a, x)" ) ; // x.a might have a getter that updates x, but that side effect is preserved correctly
1186
1248
1187
- fold_same ( "''.concat(1)" ) ;
1188
-
1189
1249
// other
1190
1250
fold_same ( "obj.concat([1,2]).concat(1)" ) ;
1191
1251
}
1192
1252
1193
1253
#[ test]
1194
- #[ ignore]
1195
1254
fn test_remove_array_literal_from_front_of_concat ( ) {
1196
- // enableTypeCheck();
1197
-
1198
- fold ( "[].concat([1,2,3],1)" , "[1,2,3].concat(1)" ) ;
1255
+ fold ( "[].concat([1,2,3],1)" , "[1,2,3,1]" ) ;
1199
1256
1200
- fold_same ( "[1,2,3].concat(returnArrayType ())" ) ;
1257
+ fold_same ( "[1,2,3].concat(foo ())" ) ;
1201
1258
// Call method with the same name as Array.prototype.concat
1202
1259
fold_same ( "obj.concat([1,2,3])" ) ;
1203
1260
1204
- fold_same ( "[].concat(1,[1,2,3])" ) ;
1205
- fold_same ( "[].concat(1)" ) ;
1206
- fold ( "[].concat([1])" , "[1].concat() " ) ;
1261
+ fold ( "[].concat(1,[1,2,3])" , "[1,1,2,3] ") ;
1262
+ fold ( "[].concat(1)" , "[1] ") ;
1263
+ fold ( "[].concat([1])" , "[1]" ) ;
1207
1264
1208
1265
// Chained folding of empty array lit
1209
- fold ( "[].concat([], [1,2,3], [4])" , "[1,2,3].concat([4]) " ) ;
1210
- fold ( "[].concat([]).concat([1]).concat([2,3])" , "[1].concat([ 2,3]) " ) ;
1266
+ fold ( "[].concat([], [1,2,3], [4])" , "[1,2,3,4] " ) ;
1267
+ fold ( "[].concat([]).concat([1]).concat([2,3])" , "[1, 2,3]" ) ;
1211
1268
1212
- // Cannot fold based on type information
1213
- fold_same ( "[].concat(returnArrayType(),1)" ) ;
1214
- fold_same ( "[].concat(returnArrayType())" ) ;
1215
- fold_same ( "[].concat(returnUnionType())" ) ;
1269
+ fold ( "[].concat(1, x)" , "[1].concat(x)" ) ; // x might be an array or an object with `Symbol.isConcatSpreadable`
1270
+ fold ( "[].concat(1, ...x)" , "[1].concat(...x)" ) ;
1271
+ fold_same ( "[].concat(x, 1)" ) ;
1216
1272
}
1217
1273
1218
1274
#[ test]
0 commit comments