Skip to content

Commit 5a14f26

Browse files
committed
more minification rules for "?:" into "||/&&"
1 parent bdd1ad7 commit 5a14f26

File tree

4 files changed

+238
-214
lines changed

4 files changed

+238
-214
lines changed

internal/js_ast/js_ast.go

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2803,6 +2803,13 @@ func SimplifyUnusedExpr(expr Expr, unsupportedFeatures compat.JSFeature, isUnbou
28032803
}
28042804

28052805
case BinOpLogicalAnd, BinOpLogicalOr, BinOpNullishCoalescing:
2806+
// If this is a boolean logical operation and the result is unused, then
2807+
// we know the left operand will only be used for its boolean value and
2808+
// can be simplified under that assumption
2809+
if e.Op != BinOpNullishCoalescing {
2810+
e.Left = SimplifyBooleanExpr(e.Left)
2811+
}
2812+
28062813
// Preserve short-circuit behavior: the left expression is only unused if
28072814
// the right expression can be completely removed. Otherwise, the left
28082815
// expression is important for the branch.
@@ -3151,3 +3158,189 @@ func TryToInsertOptionalChain(test Expr, expr Expr) bool {
31513158

31523159
return false
31533160
}
3161+
3162+
type SideEffects uint8
3163+
3164+
const (
3165+
CouldHaveSideEffects SideEffects = iota
3166+
NoSideEffects
3167+
)
3168+
3169+
func ToNullOrUndefinedWithSideEffects(data E) (isNullOrUndefined bool, sideEffects SideEffects, ok bool) {
3170+
switch e := data.(type) {
3171+
case *EInlinedEnum:
3172+
return ToNullOrUndefinedWithSideEffects(e.Value.Data)
3173+
3174+
// Never null or undefined
3175+
case *EBoolean, *ENumber, *EString, *ERegExp,
3176+
*EFunction, *EArrow, *EBigInt:
3177+
return false, NoSideEffects, true
3178+
3179+
// Never null or undefined
3180+
case *EObject, *EArray, *EClass:
3181+
return false, CouldHaveSideEffects, true
3182+
3183+
// Always null or undefined
3184+
case *ENull, *EUndefined:
3185+
return true, NoSideEffects, true
3186+
3187+
case *EUnary:
3188+
switch e.Op {
3189+
case
3190+
// Always number or bigint
3191+
UnOpPos, UnOpNeg, UnOpCpl,
3192+
UnOpPreDec, UnOpPreInc, UnOpPostDec, UnOpPostInc,
3193+
// Always boolean
3194+
UnOpNot, UnOpTypeof, UnOpDelete:
3195+
return false, CouldHaveSideEffects, true
3196+
3197+
// Always undefined
3198+
case UnOpVoid:
3199+
return true, CouldHaveSideEffects, true
3200+
}
3201+
3202+
case *EBinary:
3203+
switch e.Op {
3204+
case
3205+
// Always string or number or bigint
3206+
BinOpAdd, BinOpAddAssign,
3207+
// Always number or bigint
3208+
BinOpSub, BinOpMul, BinOpDiv, BinOpRem, BinOpPow,
3209+
BinOpSubAssign, BinOpMulAssign, BinOpDivAssign, BinOpRemAssign, BinOpPowAssign,
3210+
BinOpShl, BinOpShr, BinOpUShr,
3211+
BinOpShlAssign, BinOpShrAssign, BinOpUShrAssign,
3212+
BinOpBitwiseOr, BinOpBitwiseAnd, BinOpBitwiseXor,
3213+
BinOpBitwiseOrAssign, BinOpBitwiseAndAssign, BinOpBitwiseXorAssign,
3214+
// Always boolean
3215+
BinOpLt, BinOpLe, BinOpGt, BinOpGe, BinOpIn, BinOpInstanceof,
3216+
BinOpLooseEq, BinOpLooseNe, BinOpStrictEq, BinOpStrictNe:
3217+
return false, CouldHaveSideEffects, true
3218+
3219+
case BinOpComma:
3220+
if isNullOrUndefined, _, ok := ToNullOrUndefinedWithSideEffects(e.Right.Data); ok {
3221+
return isNullOrUndefined, CouldHaveSideEffects, true
3222+
}
3223+
}
3224+
}
3225+
3226+
return false, NoSideEffects, false
3227+
}
3228+
3229+
func ToBooleanWithSideEffects(data E) (boolean bool, SideEffects SideEffects, ok bool) {
3230+
switch e := data.(type) {
3231+
case *EInlinedEnum:
3232+
return ToBooleanWithSideEffects(e.Value.Data)
3233+
3234+
case *ENull, *EUndefined:
3235+
return false, NoSideEffects, true
3236+
3237+
case *EBoolean:
3238+
return e.Value, NoSideEffects, true
3239+
3240+
case *ENumber:
3241+
return e.Value != 0 && !math.IsNaN(e.Value), NoSideEffects, true
3242+
3243+
case *EBigInt:
3244+
return e.Value != "0", NoSideEffects, true
3245+
3246+
case *EString:
3247+
return len(e.Value) > 0, NoSideEffects, true
3248+
3249+
case *EFunction, *EArrow, *ERegExp:
3250+
return true, NoSideEffects, true
3251+
3252+
case *EObject, *EArray, *EClass:
3253+
return true, CouldHaveSideEffects, true
3254+
3255+
case *EUnary:
3256+
switch e.Op {
3257+
case UnOpVoid:
3258+
return false, CouldHaveSideEffects, true
3259+
3260+
case UnOpTypeof:
3261+
// Never an empty string
3262+
return true, CouldHaveSideEffects, true
3263+
3264+
case UnOpNot:
3265+
if boolean, SideEffects, ok := ToBooleanWithSideEffects(e.Value.Data); ok {
3266+
return !boolean, SideEffects, true
3267+
}
3268+
}
3269+
3270+
case *EBinary:
3271+
switch e.Op {
3272+
case BinOpLogicalOr:
3273+
// "anything || truthy" is truthy
3274+
if boolean, _, ok := ToBooleanWithSideEffects(e.Right.Data); ok && boolean {
3275+
return true, CouldHaveSideEffects, true
3276+
}
3277+
3278+
case BinOpLogicalAnd:
3279+
// "anything && falsy" is falsy
3280+
if boolean, _, ok := ToBooleanWithSideEffects(e.Right.Data); ok && !boolean {
3281+
return false, CouldHaveSideEffects, true
3282+
}
3283+
3284+
case BinOpComma:
3285+
// "anything, truthy/falsy" is truthy/falsy
3286+
if boolean, _, ok := ToBooleanWithSideEffects(e.Right.Data); ok {
3287+
return boolean, CouldHaveSideEffects, true
3288+
}
3289+
}
3290+
}
3291+
3292+
return false, CouldHaveSideEffects, false
3293+
}
3294+
3295+
// Simplify syntax when we know it's used inside a boolean context
3296+
func SimplifyBooleanExpr(expr Expr) Expr {
3297+
switch e := expr.Data.(type) {
3298+
case *EUnary:
3299+
if e.Op == UnOpNot {
3300+
// "!!a" => "a"
3301+
if e2, ok2 := e.Value.Data.(*EUnary); ok2 && e2.Op == UnOpNot {
3302+
return SimplifyBooleanExpr(e2.Value)
3303+
}
3304+
3305+
e.Value = SimplifyBooleanExpr(e.Value)
3306+
}
3307+
3308+
case *EBinary:
3309+
switch e.Op {
3310+
case BinOpLogicalAnd:
3311+
if boolean, SideEffects, ok := ToBooleanWithSideEffects(e.Right.Data); ok && boolean && SideEffects == NoSideEffects {
3312+
// "if (anything && truthyNoSideEffects)" => "if (anything)"
3313+
return e.Left
3314+
}
3315+
3316+
case BinOpLogicalOr:
3317+
if boolean, SideEffects, ok := ToBooleanWithSideEffects(e.Right.Data); ok && !boolean && SideEffects == NoSideEffects {
3318+
// "if (anything || falsyNoSideEffects)" => "if (anything)"
3319+
return e.Left
3320+
}
3321+
}
3322+
3323+
case *EIf:
3324+
if boolean, SideEffects, ok := ToBooleanWithSideEffects(e.Yes.Data); ok && SideEffects == NoSideEffects {
3325+
if boolean {
3326+
// "if (anything1 ? truthyNoSideEffects : anything2)" => "if (anything1 || anything2)"
3327+
return JoinWithLeftAssociativeOp(BinOpLogicalOr, e.Test, e.No)
3328+
} else {
3329+
// "if (anything1 ? falsyNoSideEffects : anything2)" => "if (!anything1 || anything2)"
3330+
return JoinWithLeftAssociativeOp(BinOpLogicalAnd, Not(e.Test), e.No)
3331+
}
3332+
}
3333+
3334+
if boolean, SideEffects, ok := ToBooleanWithSideEffects(e.No.Data); ok && SideEffects == NoSideEffects {
3335+
if boolean {
3336+
// "if (anything1 ? anything2 : truthyNoSideEffects)" => "if (!anything1 || anything2)"
3337+
return JoinWithLeftAssociativeOp(BinOpLogicalOr, Not(e.Test), e.Yes)
3338+
} else {
3339+
// "if (anything1 ? anything2 : falsyNoSideEffects)" => "if (anything1 && anything2)"
3340+
return JoinWithLeftAssociativeOp(BinOpLogicalAnd, e.Test, e.Yes)
3341+
}
3342+
}
3343+
}
3344+
3345+
return expr
3346+
}

0 commit comments

Comments
 (0)