1+ use crate :: ctx:: Ctx ;
12use oxc_allocator:: TakeIn ;
23use oxc_ast:: { NONE , ast:: * } ;
4+ use oxc_compat:: ESFeature ;
35use oxc_ecmascript:: {
46 constant_evaluation:: { ConstantEvaluation , ConstantValue } ,
57 side_effects:: MayHaveSideEffects ,
68} ;
79use oxc_span:: { ContentEq , GetSpan } ;
8- use oxc_syntax:: es_target:: ESTarget ;
9-
10- use crate :: ctx:: Ctx ;
1110
1211use super :: PeepholeOptimizations ;
1312
@@ -299,70 +298,73 @@ impl<'a> PeepholeOptimizations {
299298 }
300299
301300 // Try using the "??" or "?." operators
302- if ctx. options ( ) . target >= ESTarget :: ES2020 {
303- if let Expression :: BinaryExpression ( test_binary) = & mut expr. test {
304- if let Some ( is_negate) = match test_binary. operator {
305- BinaryOperator :: Inequality => Some ( true ) ,
306- BinaryOperator :: Equality => Some ( false ) ,
307- _ => None ,
308- } {
309- // a == null / a != null / (a = foo) == null / (a = foo) != null
310- let value_expr_with_id_name = if test_binary. left . is_null ( ) {
311- if let Some ( id) = Self :: extract_id_or_assign_to_id ( & test_binary. right )
312- . filter ( |id| !ctx. is_global_reference ( id) )
313- {
314- Some ( ( id. name , & mut test_binary. right ) )
315- } else {
316- None
317- }
318- } else if test_binary. right . is_null ( ) {
319- if let Some ( id) = Self :: extract_id_or_assign_to_id ( & test_binary. left )
320- . filter ( |id| !ctx. is_global_reference ( id) )
321- {
322- Some ( ( id. name , & mut test_binary. left ) )
323- } else {
324- None
325- }
326- } else {
327- None
328- } ;
329- if let Some ( ( target_id_name, value_expr) ) = value_expr_with_id_name {
330- // `a == null ? b : a` -> `a ?? b`
331- // `a != null ? a : b` -> `a ?? b`
332- // `(a = foo) == null ? b : a` -> `(a = foo) ?? b`
333- // `(a = foo) != null ? a : b` -> `(a = foo) ?? b`
334- let maybe_same_id_expr =
301+ if ( ctx. supports_feature ( ESFeature :: ES2020NullishCoalescingOperator )
302+ || ctx. supports_feature ( ESFeature :: ES2020OptionalChaining ) )
303+ && let Expression :: BinaryExpression ( test_binary) = & mut expr. test
304+ && let Some ( is_negate) = match test_binary. operator {
305+ BinaryOperator :: Inequality => Some ( true ) ,
306+ BinaryOperator :: Equality => Some ( false ) ,
307+ _ => None ,
308+ }
309+ {
310+ // a == null / a != null / (a = foo) == null / (a = foo) != null
311+ let value_expr_with_id_name = if test_binary. left . is_null ( ) {
312+ if let Some ( id) = Self :: extract_id_or_assign_to_id ( & test_binary. right )
313+ . filter ( |id| !ctx. is_global_reference ( id) )
314+ {
315+ Some ( ( id. name , & mut test_binary. right ) )
316+ } else {
317+ None
318+ }
319+ } else if test_binary. right . is_null ( ) {
320+ if let Some ( id) = Self :: extract_id_or_assign_to_id ( & test_binary. left )
321+ . filter ( |id| !ctx. is_global_reference ( id) )
322+ {
323+ Some ( ( id. name , & mut test_binary. left ) )
324+ } else {
325+ None
326+ }
327+ } else {
328+ None
329+ } ;
330+ if let Some ( ( target_id_name, value_expr) ) = value_expr_with_id_name {
331+ if ctx. supports_feature ( ESFeature :: ES2020NullishCoalescingOperator ) {
332+ // `a == null ? b : a` -> `a ?? b`
333+ // `a != null ? a : b` -> `a ?? b`
334+ // `(a = foo) == null ? b : a` -> `(a = foo) ?? b`
335+ // `(a = foo) != null ? a : b` -> `(a = foo) ?? b`
336+ let maybe_same_id_expr =
337+ if is_negate { & mut expr. consequent } else { & mut expr. alternate } ;
338+ if maybe_same_id_expr. is_specific_id ( & target_id_name) {
339+ return Some ( ctx. ast . expression_logical (
340+ expr. span ,
341+ value_expr. take_in ( ctx. ast ) ,
342+ LogicalOperator :: Coalesce ,
343+ if is_negate {
344+ expr. alternate . take_in ( ctx. ast )
345+ } else {
346+ expr. consequent . take_in ( ctx. ast )
347+ } ,
348+ ) ) ;
349+ }
350+ }
351+ if ctx. supports_feature ( ESFeature :: ES2020OptionalChaining ) {
352+ // "a == null ? undefined : a.b.c[d](e)" => "a?.b.c[d](e)"
353+ // "a != null ? a.b.c[d](e) : undefined" => "a?.b.c[d](e)"
354+ // "(a = foo) == null ? undefined : a.b.c[d](e)" => "(a = foo)?.b.c[d](e)"
355+ // "(a = foo) != null ? a.b.c[d](e) : undefined" => "(a = foo)?.b.c[d](e)"
356+ let maybe_undefined_expr =
357+ if is_negate { & expr. alternate } else { & expr. consequent } ;
358+ if ctx. is_expression_undefined ( maybe_undefined_expr) {
359+ let expr_to_inject_optional_chaining =
335360 if is_negate { & mut expr. consequent } else { & mut expr. alternate } ;
336- if maybe_same_id_expr. is_specific_id ( & target_id_name) {
337- return Some ( ctx. ast . expression_logical (
338- expr. span ,
339- value_expr. take_in ( ctx. ast ) ,
340- LogicalOperator :: Coalesce ,
341- if is_negate {
342- expr. alternate . take_in ( ctx. ast )
343- } else {
344- expr. consequent . take_in ( ctx. ast )
345- } ,
346- ) ) ;
347- }
348-
349- // "a == null ? undefined : a.b.c[d](e)" => "a?.b.c[d](e)"
350- // "a != null ? a.b.c[d](e) : undefined" => "a?.b.c[d](e)"
351- // "(a = foo) == null ? undefined : a.b.c[d](e)" => "(a = foo)?.b.c[d](e)"
352- // "(a = foo) != null ? a.b.c[d](e) : undefined" => "(a = foo)?.b.c[d](e)"
353- let maybe_undefined_expr =
354- if is_negate { & expr. alternate } else { & expr. consequent } ;
355- if ctx. is_expression_undefined ( maybe_undefined_expr) {
356- let expr_to_inject_optional_chaining =
357- if is_negate { & mut expr. consequent } else { & mut expr. alternate } ;
358- if Self :: inject_optional_chaining_if_matched (
359- & target_id_name,
360- value_expr,
361- expr_to_inject_optional_chaining,
362- ctx,
363- ) {
364- return Some ( expr_to_inject_optional_chaining. take_in ( ctx. ast ) ) ;
365- }
361+ if Self :: inject_optional_chaining_if_matched (
362+ & target_id_name,
363+ value_expr,
364+ expr_to_inject_optional_chaining,
365+ ctx,
366+ ) {
367+ return Some ( expr_to_inject_optional_chaining. take_in ( ctx. ast ) ) ;
366368 }
367369 }
368370 }
@@ -590,18 +592,7 @@ impl<'a> PeepholeOptimizations {
590592
591593#[ cfg( test) ]
592594mod test {
593- use oxc_syntax:: es_target:: ESTarget ;
594-
595- use crate :: {
596- CompressOptions ,
597- tester:: { test, test_options, test_same} ,
598- } ;
599-
600- fn test_es2019 ( source_text : & str , expected : & str ) {
601- let target = ESTarget :: ES2019 ;
602- let options = CompressOptions { target, ..CompressOptions :: default ( ) } ;
603- test_options ( source_text, expected, & options) ;
604- }
595+ use crate :: tester:: { test, test_same, test_target} ;
605596
606597 #[ test]
607598 fn test_minimize_expr_condition ( ) {
@@ -663,7 +654,7 @@ mod test {
663654 test ( "var a; a != null ? a : b" , "var a; a ?? b" ) ;
664655 test ( "var a; (a = _a) != null ? a : b" , "var a; (a = _a) ?? b" ) ;
665656 test ( "v = a != null ? a : b" , "v = a == null ? b : a" ) ; // accessing global `a` may have a getter with side effects
666- test_es2019 ( "var a; v = a != null ? a : b" , "var a; v = a == null ? b : a" ) ;
657+ test_target ( "var a; v = a != null ? a : b" , "var a; v = a == null ? b : a" , "chrome79 ") ;
667658 test ( "var a; v = a != null ? a.b.c[d](e) : undefined" , "var a; v = a?.b.c[d](e)" ) ;
668659 test (
669660 "var a; v = (a = _a) != null ? a.b.c[d](e) : undefined" ,
@@ -674,9 +665,10 @@ mod test {
674665 "var a, undefined = 1; v = a != null ? a.b.c[d](e) : undefined" ,
675666 "var a; v = a == null ? 1 : a.b.c[d](e)" ,
676667 ) ;
677- test_es2019 (
668+ test_target (
678669 "var a; v = a != null ? a.b.c[d](e) : undefined" ,
679670 "var a; v = a == null ? void 0 : a.b.c[d](e)" ,
671+ "chrome79" ,
680672 ) ;
681673 test ( "v = cmp !== 0 ? cmp : (bar, cmp);" , "v = (cmp === 0 && bar, cmp);" ) ;
682674 test ( "v = cmp === 0 ? cmp : (bar, cmp);" , "v = (cmp === 0 || bar, cmp);" ) ;
0 commit comments