@@ -907,6 +907,7 @@ impl<'a> LatePeepholeOptimizations {
907
907
908
908
if let Some ( folded_expr) = match expr {
909
909
Expression :: BooleanLiteral ( _) => Self :: try_compress_boolean ( expr, ctx) ,
910
+ Expression :: ArrayExpression ( _) => Self :: try_compress_array_expression ( expr, ctx) ,
910
911
_ => None ,
911
912
} {
912
913
* expr = folded_expr;
@@ -939,6 +940,78 @@ impl<'a> LatePeepholeOptimizations {
939
940
Some ( ctx. ast . expression_unary ( lit. span , UnaryOperator :: LogicalNot , num) )
940
941
}
941
942
943
+ /// Transforms long array expression with string literals to `"str1,str2".split(',')`
944
+ fn try_compress_array_expression (
945
+ expr : & mut Expression < ' a > ,
946
+ ctx : Ctx < ' a , ' _ > ,
947
+ ) -> Option < Expression < ' a > > {
948
+ // this threshold is chosen by hand by checking the minsize output
949
+ const THRESHOLD : usize = 40 ;
950
+
951
+ let Expression :: ArrayExpression ( array) = expr else { unreachable ! ( ) } ;
952
+
953
+ let is_all_string = array
954
+ . elements
955
+ . iter ( )
956
+ . all ( |element| element. as_expression ( ) . is_some_and ( Expression :: is_string_literal) ) ;
957
+ if !is_all_string {
958
+ return None ;
959
+ }
960
+
961
+ let element_count = array. elements . len ( ) ;
962
+ // replace with `.split` only when the saved size is great enough
963
+ // because using `.split` in some places and not in others may cause gzipped size to be bigger
964
+ let can_save = element_count * 2 > ".split('.')" . len ( ) + THRESHOLD ;
965
+ if !can_save {
966
+ return None ;
967
+ }
968
+
969
+ let strings = array. elements . iter ( ) . map ( |element| {
970
+ let Expression :: StringLiteral ( str) = element. to_expression ( ) else { unreachable ! ( ) } ;
971
+ str. value . as_str ( )
972
+ } ) ;
973
+ let delimiter = Self :: pick_delimiter ( & strings) ?;
974
+
975
+ let concatenated_string = strings. collect :: < std:: vec:: Vec < _ > > ( ) . join ( delimiter) ;
976
+
977
+ // "str1,str2".split(',')
978
+ Some ( ctx. ast . expression_call (
979
+ expr. span ( ) ,
980
+ Expression :: StaticMemberExpression ( ctx. ast . alloc_static_member_expression (
981
+ expr. span ( ) ,
982
+ ctx. ast . expression_string_literal (
983
+ expr. span ( ) ,
984
+ ctx. ast . atom ( & concatenated_string) ,
985
+ None ,
986
+ ) ,
987
+ ctx. ast . identifier_name ( expr. span ( ) , "split" ) ,
988
+ false ,
989
+ ) ) ,
990
+ Option :: < TSTypeParameterInstantiation > :: None ,
991
+ ctx. ast . vec1 ( Argument :: from ( ctx. ast . expression_string_literal (
992
+ expr. span ( ) ,
993
+ ctx. ast . atom ( delimiter) ,
994
+ None ,
995
+ ) ) ) ,
996
+ false ,
997
+ ) )
998
+ }
999
+
1000
+ fn pick_delimiter < ' s > (
1001
+ strings : & ( impl Iterator < Item = & ' s str > + Clone ) ,
1002
+ ) -> Option < & ' static str > {
1003
+ // These delimiters are chars that appears a lot in the program
1004
+ // therefore probably have a small Huffman encoding.
1005
+ const DELIMITERS : [ & str ; 5 ] = [ "." , "," , "(" , ")" , " " ] ;
1006
+
1007
+ let is_all_length_1 = strings. clone ( ) . all ( |s| s. len ( ) == 1 ) ;
1008
+ if is_all_length_1 {
1009
+ return Some ( "" ) ;
1010
+ }
1011
+
1012
+ DELIMITERS . into_iter ( ) . find ( |& delimiter| strings. clone ( ) . all ( |s| !s. contains ( delimiter) ) )
1013
+ }
1014
+
942
1015
pub fn substitute_catch_clause ( & mut self , catch : & mut CatchClause < ' a > , ctx : Ctx < ' a , ' _ > ) {
943
1016
if self . target >= ESTarget :: ES2019 {
944
1017
if let Some ( param) = & catch. param {
@@ -1233,20 +1306,33 @@ mod test {
1233
1306
}
1234
1307
1235
1308
#[ test]
1236
- #[ ignore]
1237
1309
fn test_string_array_splitting ( ) {
1238
- test_same ( "var x=['1','2','3','4']" ) ;
1239
- test_same ( "var x=['1','2','3','4','5']" ) ;
1240
- test ( "var x=['1','2','3','4','5','6']" , "var x='123456'.split('')" ) ;
1241
- test ( "var x=['1','2','3','4','5','00']" , "var x='1 2 3 4 5 00'.split(' ')" ) ;
1242
- test ( "var x=['1','2','3','4','5','6','7']" , "var x='1234567'.split('')" ) ;
1243
- test ( "var x=['1','2','3','4','5','6','00']" , "var x='1 2 3 4 5 6 00'.split(' ')" ) ;
1244
- test ( "var x=[' ,',',',',',',',',',',']" , "var x=' ,;,;,;,;,;,'.split(';')" ) ;
1245
- test ( "var x=[',,',' ',',',',',',',',']" , "var x=',,; ;,;,;,;,'.split(';')" ) ;
1246
- test ( "var x=['a,',' ',',',',',',',',']" , "var x='a,; ;,;,;,;,'.split(';')" ) ;
1310
+ const REPEAT : usize = 20 ;
1311
+ let additional_args = ",'1'" . repeat ( REPEAT ) ;
1312
+ let test_with_longer_args =
1313
+ |source_text_partial : & str , expected_partial : & str , delimiter : & str | {
1314
+ let expected = & format ! (
1315
+ "var x='{expected_partial}{}'.split('{delimiter}')" ,
1316
+ format!( "{delimiter}1" ) . repeat( REPEAT )
1317
+ ) ;
1318
+ test ( & format ! ( "var x=[{source_text_partial}{additional_args}]" ) , expected) ;
1319
+ } ;
1320
+ let test_same_with_longer_args = |source_text_partial : & str | {
1321
+ test_same ( & format ! ( "var x=[{source_text_partial}{additional_args}]" ) ) ;
1322
+ } ;
1323
+
1324
+ test_same_with_longer_args ( "'1','2','3','4'" ) ;
1325
+ test_same_with_longer_args ( "'1','2','3','4','5'" ) ;
1326
+ test_with_longer_args ( "'1','2','3','4','5','6'" , "123456" , "" ) ;
1327
+ test_with_longer_args ( "'1','2','3','4','5','00'" , "1.2.3.4.5.00" , "." ) ;
1328
+ test_with_longer_args ( "'1','2','3','4','5','6','7'" , "1234567" , "" ) ;
1329
+ test_with_longer_args ( "'1','2','3','4','5','6','00'" , "1.2.3.4.5.6.00" , "." ) ;
1330
+ test_with_longer_args ( "'.,',',',',',',',',',','" , ".,(,(,(,(,(," , "(" ) ;
1331
+ test_with_longer_args ( "',,','.',',',',',',',','" , ",,(.(,(,(,(," , "(" ) ;
1332
+ test_with_longer_args ( "'a,','.',',',',',',',','" , "a,(.(,(,(,(," , "(" ) ;
1247
1333
1248
1334
// all possible delimiters used, leave it alone
1249
- test_same ( "var x=[', ', ' ', '; ', '{ ', '}'] ") ;
1335
+ test_same_with_longer_args ( "'. ', ', ', '( ', ') ', ' ' ") ;
1250
1336
}
1251
1337
1252
1338
#[ test]
0 commit comments