7
7
8
8
"github.com/evanw/esbuild/internal/ast"
9
9
"github.com/evanw/esbuild/internal/compat"
10
+ "github.com/evanw/esbuild/internal/helpers"
10
11
"github.com/evanw/esbuild/internal/logger"
11
12
)
12
13
@@ -2623,10 +2624,10 @@ func IsPrimitiveWithSideEffects(data E) bool {
2623
2624
}
2624
2625
2625
2626
// This will return a nil expression if the expression can be totally removed
2626
- func SimplifyUnusedExpr (expr Expr , isUnbound func (Ref ) bool ) Expr {
2627
+ func SimplifyUnusedExpr (expr Expr , unsupportedFeatures compat. JSFeature , isUnbound func (Ref ) bool ) Expr {
2627
2628
switch e := expr .Data .(type ) {
2628
2629
case * EInlinedEnum :
2629
- return SimplifyUnusedExpr (e .Value , isUnbound )
2630
+ return SimplifyUnusedExpr (e .Value , unsupportedFeatures , isUnbound )
2630
2631
2631
2632
case * ENull , * EUndefined , * EMissing , * EBoolean , * ENumber , * EBigInt ,
2632
2633
* EString , * EThis , * ERegExp , * EFunction , * EArrow , * EImportMeta :
@@ -2658,7 +2659,7 @@ func SimplifyUnusedExpr(expr Expr, isUnbound func(Ref) bool) Expr {
2658
2659
comma = JoinWithComma (comma , Expr {Loc : templateLoc , Data : template })
2659
2660
template = nil
2660
2661
}
2661
- comma = JoinWithComma (comma , SimplifyUnusedExpr (part .Value , isUnbound ))
2662
+ comma = JoinWithComma (comma , SimplifyUnusedExpr (part .Value , unsupportedFeatures , isUnbound ))
2662
2663
continue
2663
2664
}
2664
2665
@@ -2684,7 +2685,7 @@ func SimplifyUnusedExpr(expr Expr, isUnbound func(Ref) bool) Expr {
2684
2685
if _ , ok := spread .Data .(* ESpread ); ok {
2685
2686
end := 0
2686
2687
for _ , item := range e .Items {
2687
- item = SimplifyUnusedExpr (item , isUnbound )
2688
+ item = SimplifyUnusedExpr (item , unsupportedFeatures , isUnbound )
2688
2689
if item .Data != nil {
2689
2690
e .Items [end ] = item
2690
2691
end ++
@@ -2699,7 +2700,7 @@ func SimplifyUnusedExpr(expr Expr, isUnbound func(Ref) bool) Expr {
2699
2700
// array items with side effects. Apply this simplification recursively.
2700
2701
var result Expr
2701
2702
for _ , item := range e .Items {
2702
- result = JoinWithComma (result , SimplifyUnusedExpr (item , isUnbound ))
2703
+ result = JoinWithComma (result , SimplifyUnusedExpr (item , unsupportedFeatures , isUnbound ))
2703
2704
}
2704
2705
return result
2705
2706
@@ -2713,7 +2714,7 @@ func SimplifyUnusedExpr(expr Expr, isUnbound func(Ref) bool) Expr {
2713
2714
for _ , property := range e .Properties {
2714
2715
// Spread properties must always be evaluated
2715
2716
if property .Kind != PropertySpread {
2716
- value := SimplifyUnusedExpr (property .ValueOrNil , isUnbound )
2717
+ value := SimplifyUnusedExpr (property .ValueOrNil , unsupportedFeatures , isUnbound )
2717
2718
if value .Data != nil {
2718
2719
// Keep the value
2719
2720
property .ValueOrNil = value
@@ -2745,17 +2746,17 @@ func SimplifyUnusedExpr(expr Expr, isUnbound func(Ref) bool) Expr {
2745
2746
Right : Expr {Loc : property .Key .Loc , Data : & EString {}},
2746
2747
}})
2747
2748
}
2748
- result = JoinWithComma (result , SimplifyUnusedExpr (property .ValueOrNil , isUnbound ))
2749
+ result = JoinWithComma (result , SimplifyUnusedExpr (property .ValueOrNil , unsupportedFeatures , isUnbound ))
2749
2750
}
2750
2751
return result
2751
2752
2752
2753
case * EIf :
2753
- e .Yes = SimplifyUnusedExpr (e .Yes , isUnbound )
2754
- e .No = SimplifyUnusedExpr (e .No , isUnbound )
2754
+ e .Yes = SimplifyUnusedExpr (e .Yes , unsupportedFeatures , isUnbound )
2755
+ e .No = SimplifyUnusedExpr (e .No , unsupportedFeatures , isUnbound )
2755
2756
2756
2757
// "foo() ? 1 : 2" => "foo()"
2757
2758
if e .Yes .Data == nil && e .No .Data == nil {
2758
- return SimplifyUnusedExpr (e .Test , isUnbound )
2759
+ return SimplifyUnusedExpr (e .Test , unsupportedFeatures , isUnbound )
2759
2760
}
2760
2761
2761
2762
// "foo() ? 1 : bar()" => "foo() || bar()"
@@ -2773,7 +2774,7 @@ func SimplifyUnusedExpr(expr Expr, isUnbound func(Ref) bool) Expr {
2773
2774
// These operators must not have any type conversions that can execute code
2774
2775
// such as "toString" or "valueOf". They must also never throw any exceptions.
2775
2776
case UnOpVoid , UnOpNot :
2776
- return SimplifyUnusedExpr (e .Value , isUnbound )
2777
+ return SimplifyUnusedExpr (e .Value , unsupportedFeatures , isUnbound )
2777
2778
2778
2779
case UnOpTypeof :
2779
2780
if _ , ok := e .Value .Data .(* EIdentifier ); ok {
@@ -2782,32 +2783,52 @@ func SimplifyUnusedExpr(expr Expr, isUnbound func(Ref) bool) Expr {
2782
2783
// "typeof x" is special-cased in the standard to never throw.
2783
2784
return Expr {}
2784
2785
}
2785
- return SimplifyUnusedExpr (e .Value , isUnbound )
2786
+ return SimplifyUnusedExpr (e .Value , unsupportedFeatures , isUnbound )
2786
2787
}
2787
2788
2788
2789
case * EBinary :
2789
2790
switch e .Op {
2790
2791
// These operators must not have any type conversions that can execute code
2791
2792
// such as "toString" or "valueOf". They must also never throw any exceptions.
2792
2793
case BinOpStrictEq , BinOpStrictNe , BinOpComma :
2793
- return JoinWithComma (SimplifyUnusedExpr (e .Left , isUnbound ), SimplifyUnusedExpr (e .Right , isUnbound ))
2794
+ return JoinWithComma (SimplifyUnusedExpr (e .Left , unsupportedFeatures , isUnbound ), SimplifyUnusedExpr (e .Right , unsupportedFeatures , isUnbound ))
2794
2795
2795
2796
// We can simplify "==" and "!=" even though they can call "toString" and/or
2796
2797
// "valueOf" if we can statically determine that the types of both sides are
2797
2798
// primitives. In that case there won't be any chance for user-defined
2798
2799
// "toString" and/or "valueOf" to be called.
2799
2800
case BinOpLooseEq , BinOpLooseNe :
2800
2801
if IsPrimitiveWithSideEffects (e .Left .Data ) && IsPrimitiveWithSideEffects (e .Right .Data ) {
2801
- return JoinWithComma (SimplifyUnusedExpr (e .Left , isUnbound ), SimplifyUnusedExpr (e .Right , isUnbound ))
2802
+ return JoinWithComma (SimplifyUnusedExpr (e .Left , unsupportedFeatures , isUnbound ), SimplifyUnusedExpr (e .Right , unsupportedFeatures , isUnbound ))
2802
2803
}
2803
2804
2804
2805
case BinOpLogicalAnd , BinOpLogicalOr , BinOpNullishCoalescing :
2805
2806
// Preserve short-circuit behavior: the left expression is only unused if
2806
2807
// the right expression can be completely removed. Otherwise, the left
2807
2808
// expression is important for the branch.
2808
- e .Right = SimplifyUnusedExpr (e .Right , isUnbound )
2809
+ e .Right = SimplifyUnusedExpr (e .Right , unsupportedFeatures , isUnbound )
2809
2810
if e .Right .Data == nil {
2810
- return SimplifyUnusedExpr (e .Left , isUnbound )
2811
+ return SimplifyUnusedExpr (e .Left , unsupportedFeatures , isUnbound )
2812
+ }
2813
+
2814
+ // Try to take advantage of the optional chain operator to shorten code
2815
+ if ! unsupportedFeatures .Has (compat .OptionalChain ) {
2816
+ if binary , ok := e .Left .Data .(* EBinary ); ok {
2817
+ // "a != null && a.b()" => "a?.b()"
2818
+ // "a == null || a.b()" => "a?.b()"
2819
+ if (binary .Op == BinOpLooseNe && e .Op == BinOpLogicalAnd ) || (binary .Op == BinOpLooseEq && e .Op == BinOpLogicalOr ) {
2820
+ var test Expr
2821
+ if _ , ok := binary .Right .Data .(* ENull ); ok {
2822
+ test = binary .Left
2823
+ } else if _ , ok := binary .Left .Data .(* ENull ); ok {
2824
+ test = binary .Right
2825
+ }
2826
+ if id , ok := test .Data .(* EIdentifier ); ok && ! id .MustKeepDueToWithStmt &&
2827
+ (id .CanBeRemovedIfUnused || ! isUnbound (id .Ref )) && TryToInsertOptionalChain (test , e .Right ) {
2828
+ return e .Right
2829
+ }
2830
+ }
2831
+ }
2811
2832
}
2812
2833
2813
2834
case BinOpAdd :
@@ -2822,7 +2843,7 @@ func SimplifyUnusedExpr(expr Expr, isUnbound func(Ref) bool) Expr {
2822
2843
if e .CanBeUnwrappedIfUnused {
2823
2844
expr = Expr {}
2824
2845
for _ , arg := range e .Args {
2825
- expr = JoinWithComma (expr , SimplifyUnusedExpr (arg , isUnbound ))
2846
+ expr = JoinWithComma (expr , SimplifyUnusedExpr (arg , unsupportedFeatures , isUnbound ))
2826
2847
}
2827
2848
}
2828
2849
@@ -2877,7 +2898,7 @@ func SimplifyUnusedExpr(expr Expr, isUnbound func(Ref) bool) Expr {
2877
2898
if e .CanBeUnwrappedIfUnused {
2878
2899
expr = Expr {}
2879
2900
for _ , arg := range e .Args {
2880
- expr = JoinWithComma (expr , SimplifyUnusedExpr (arg , isUnbound ))
2901
+ expr = JoinWithComma (expr , SimplifyUnusedExpr (arg , unsupportedFeatures , isUnbound ))
2881
2902
}
2882
2903
}
2883
2904
}
@@ -2980,3 +3001,153 @@ func ExtractNumericValues(left Expr, right Expr) (float64, float64, bool) {
2980
3001
}
2981
3002
return 0 , 0 , false
2982
3003
}
3004
+
3005
+ // Returns "equal, ok". If "ok" is false, then nothing is known about the two
3006
+ // values. If "ok" is true, the equality or inequality of the two values is
3007
+ // stored in "equal".
3008
+ func CheckEqualityIfNoSideEffects (left E , right E ) (bool , bool ) {
3009
+ if r , ok := right .(* EInlinedEnum ); ok {
3010
+ return CheckEqualityIfNoSideEffects (left , r .Value .Data )
3011
+ }
3012
+
3013
+ switch l := left .(type ) {
3014
+ case * EInlinedEnum :
3015
+ return CheckEqualityIfNoSideEffects (l .Value .Data , right )
3016
+
3017
+ case * ENull :
3018
+ _ , ok := right .(* ENull )
3019
+ return ok , ok
3020
+
3021
+ case * EUndefined :
3022
+ _ , ok := right .(* EUndefined )
3023
+ return ok , ok
3024
+
3025
+ case * EBoolean :
3026
+ r , ok := right .(* EBoolean )
3027
+ return ok && l .Value == r .Value , ok
3028
+
3029
+ case * ENumber :
3030
+ r , ok := right .(* ENumber )
3031
+ return ok && l .Value == r .Value , ok
3032
+
3033
+ case * EBigInt :
3034
+ r , ok := right .(* EBigInt )
3035
+ return ok && l .Value == r .Value , ok
3036
+
3037
+ case * EString :
3038
+ r , ok := right .(* EString )
3039
+ return ok && helpers .UTF16EqualsUTF16 (l .Value , r .Value ), ok
3040
+ }
3041
+
3042
+ return false , false
3043
+ }
3044
+
3045
+ func ValuesLookTheSame (left E , right E ) bool {
3046
+ if b , ok := right .(* EInlinedEnum ); ok {
3047
+ return ValuesLookTheSame (left , b .Value .Data )
3048
+ }
3049
+
3050
+ switch a := left .(type ) {
3051
+ case * EInlinedEnum :
3052
+ return ValuesLookTheSame (a .Value .Data , right )
3053
+
3054
+ case * EIdentifier :
3055
+ if b , ok := right .(* EIdentifier ); ok && a .Ref == b .Ref {
3056
+ return true
3057
+ }
3058
+
3059
+ case * EDot :
3060
+ if b , ok := right .(* EDot ); ok && a .HasSameFlagsAs (b ) &&
3061
+ a .Name == b .Name && ValuesLookTheSame (a .Target .Data , b .Target .Data ) {
3062
+ return true
3063
+ }
3064
+
3065
+ case * EIndex :
3066
+ if b , ok := right .(* EIndex ); ok && a .HasSameFlagsAs (b ) &&
3067
+ ValuesLookTheSame (a .Target .Data , b .Target .Data ) && ValuesLookTheSame (a .Index .Data , b .Index .Data ) {
3068
+ return true
3069
+ }
3070
+
3071
+ case * EIf :
3072
+ if b , ok := right .(* EIf ); ok && ValuesLookTheSame (a .Test .Data , b .Test .Data ) &&
3073
+ ValuesLookTheSame (a .Yes .Data , b .Yes .Data ) && ValuesLookTheSame (a .No .Data , b .No .Data ) {
3074
+ return true
3075
+ }
3076
+
3077
+ case * EUnary :
3078
+ if b , ok := right .(* EUnary ); ok && a .Op == b .Op && ValuesLookTheSame (a .Value .Data , b .Value .Data ) {
3079
+ return true
3080
+ }
3081
+
3082
+ case * EBinary :
3083
+ if b , ok := right .(* EBinary ); ok && a .Op == b .Op && ValuesLookTheSame (a .Left .Data , b .Left .Data ) &&
3084
+ ValuesLookTheSame (a .Right .Data , b .Right .Data ) {
3085
+ return true
3086
+ }
3087
+
3088
+ case * ECall :
3089
+ if b , ok := right .(* ECall ); ok && a .HasSameFlagsAs (b ) &&
3090
+ len (a .Args ) == len (b .Args ) && ValuesLookTheSame (a .Target .Data , b .Target .Data ) {
3091
+ for i := range a .Args {
3092
+ if ! ValuesLookTheSame (a .Args [i ].Data , b .Args [i ].Data ) {
3093
+ return false
3094
+ }
3095
+ }
3096
+ return true
3097
+ }
3098
+
3099
+ // Special-case to distinguish between negative an non-negative zero when mangling
3100
+ // "a ? -0 : 0" => "a ? -0 : 0"
3101
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness
3102
+ case * ENumber :
3103
+ b , ok := right .(* ENumber )
3104
+ if ok && a .Value == 0 && b .Value == 0 && math .Signbit (a .Value ) != math .Signbit (b .Value ) {
3105
+ return false
3106
+ }
3107
+ }
3108
+
3109
+ equal , ok := CheckEqualityIfNoSideEffects (left , right )
3110
+ return ok && equal
3111
+ }
3112
+
3113
+ func TryToInsertOptionalChain (test Expr , expr Expr ) bool {
3114
+ switch e := expr .Data .(type ) {
3115
+ case * EDot :
3116
+ if ValuesLookTheSame (test .Data , e .Target .Data ) {
3117
+ e .OptionalChain = OptionalChainStart
3118
+ return true
3119
+ }
3120
+ if TryToInsertOptionalChain (test , e .Target ) {
3121
+ if e .OptionalChain == OptionalChainNone {
3122
+ e .OptionalChain = OptionalChainContinue
3123
+ }
3124
+ return true
3125
+ }
3126
+
3127
+ case * EIndex :
3128
+ if ValuesLookTheSame (test .Data , e .Target .Data ) {
3129
+ e .OptionalChain = OptionalChainStart
3130
+ return true
3131
+ }
3132
+ if TryToInsertOptionalChain (test , e .Target ) {
3133
+ if e .OptionalChain == OptionalChainNone {
3134
+ e .OptionalChain = OptionalChainContinue
3135
+ }
3136
+ return true
3137
+ }
3138
+
3139
+ case * ECall :
3140
+ if ValuesLookTheSame (test .Data , e .Target .Data ) {
3141
+ e .OptionalChain = OptionalChainStart
3142
+ return true
3143
+ }
3144
+ if TryToInsertOptionalChain (test , e .Target ) {
3145
+ if e .OptionalChain == OptionalChainNone {
3146
+ e .OptionalChain = OptionalChainContinue
3147
+ }
3148
+ return true
3149
+ }
3150
+ }
3151
+
3152
+ return false
3153
+ }
0 commit comments