github.com/evanw/esbuild@v0.21.4/internal/js_ast/js_ast_helpers.go (about) 1 package js_ast 2 3 import ( 4 "math" 5 "strconv" 6 "strings" 7 8 "github.com/evanw/esbuild/internal/ast" 9 "github.com/evanw/esbuild/internal/compat" 10 "github.com/evanw/esbuild/internal/helpers" 11 "github.com/evanw/esbuild/internal/logger" 12 ) 13 14 type HelperContext struct { 15 isUnbound func(ast.Ref) bool 16 } 17 18 func MakeHelperContext(isUnbound func(ast.Ref) bool) HelperContext { 19 return HelperContext{ 20 isUnbound: isUnbound, 21 } 22 } 23 24 // If this returns true, then calling this expression captures the target of 25 // the property access as "this" when calling the function in the property. 26 func IsPropertyAccess(expr Expr) bool { 27 switch expr.Data.(type) { 28 case *EDot, *EIndex: 29 return true 30 } 31 return false 32 } 33 34 func IsOptionalChain(value Expr) bool { 35 switch e := value.Data.(type) { 36 case *EDot: 37 return e.OptionalChain != OptionalChainNone 38 case *EIndex: 39 return e.OptionalChain != OptionalChainNone 40 case *ECall: 41 return e.OptionalChain != OptionalChainNone 42 } 43 return false 44 } 45 46 func Assign(a Expr, b Expr) Expr { 47 return Expr{Loc: a.Loc, Data: &EBinary{Op: BinOpAssign, Left: a, Right: b}} 48 } 49 50 func AssignStmt(a Expr, b Expr) Stmt { 51 return Stmt{Loc: a.Loc, Data: &SExpr{Value: Assign(a, b)}} 52 } 53 54 // Wraps the provided expression in the "!" prefix operator. The expression 55 // will potentially be simplified to avoid generating unnecessary extra "!" 56 // operators. For example, calling this with "!!x" will return "!x" instead 57 // of returning "!!!x". 58 func Not(expr Expr) Expr { 59 if result, ok := MaybeSimplifyNot(expr); ok { 60 return result 61 } 62 return Expr{Loc: expr.Loc, Data: &EUnary{Op: UnOpNot, Value: expr}} 63 } 64 65 // The given "expr" argument should be the operand of a "!" prefix operator 66 // (i.e. the "x" in "!x"). This returns a simplified expression for the 67 // whole operator (i.e. the "!x") if it can be simplified, or false if not. 68 // It's separate from "Not()" above to avoid allocation on failure in case 69 // that is undesired. 70 // 71 // This function intentionally avoids mutating the input AST so it can be 72 // called after the AST has been frozen (i.e. after parsing ends). 73 func MaybeSimplifyNot(expr Expr) (Expr, bool) { 74 switch e := expr.Data.(type) { 75 case *EAnnotation: 76 return MaybeSimplifyNot(e.Value) 77 78 case *EInlinedEnum: 79 if value, ok := MaybeSimplifyNot(e.Value); ok { 80 return value, true 81 } 82 83 case *ENull, *EUndefined: 84 return Expr{Loc: expr.Loc, Data: &EBoolean{Value: true}}, true 85 86 case *EBoolean: 87 return Expr{Loc: expr.Loc, Data: &EBoolean{Value: !e.Value}}, true 88 89 case *ENumber: 90 return Expr{Loc: expr.Loc, Data: &EBoolean{Value: e.Value == 0 || math.IsNaN(e.Value)}}, true 91 92 case *EBigInt: 93 if equal, ok := CheckEqualityBigInt(e.Value, "0"); ok { 94 return Expr{Loc: expr.Loc, Data: &EBoolean{Value: equal}}, true 95 } 96 97 case *EString: 98 return Expr{Loc: expr.Loc, Data: &EBoolean{Value: len(e.Value) == 0}}, true 99 100 case *EFunction, *EArrow, *ERegExp: 101 return Expr{Loc: expr.Loc, Data: &EBoolean{Value: false}}, true 102 103 case *EUnary: 104 // "!!!a" => "!a" 105 if e.Op == UnOpNot && KnownPrimitiveType(e.Value.Data) == PrimitiveBoolean { 106 return e.Value, true 107 } 108 109 case *EBinary: 110 // Make sure that these transformations are all safe for special values. 111 // For example, "!(a < b)" is not the same as "a >= b" if a and/or b are 112 // NaN (or undefined, or null, or possibly other problem cases too). 113 switch e.Op { 114 case BinOpLooseEq: 115 // "!(a == b)" => "a != b" 116 return Expr{Loc: expr.Loc, Data: &EBinary{Op: BinOpLooseNe, Left: e.Left, Right: e.Right}}, true 117 118 case BinOpLooseNe: 119 // "!(a != b)" => "a == b" 120 return Expr{Loc: expr.Loc, Data: &EBinary{Op: BinOpLooseEq, Left: e.Left, Right: e.Right}}, true 121 122 case BinOpStrictEq: 123 // "!(a === b)" => "a !== b" 124 return Expr{Loc: expr.Loc, Data: &EBinary{Op: BinOpStrictNe, Left: e.Left, Right: e.Right}}, true 125 126 case BinOpStrictNe: 127 // "!(a !== b)" => "a === b" 128 return Expr{Loc: expr.Loc, Data: &EBinary{Op: BinOpStrictEq, Left: e.Left, Right: e.Right}}, true 129 130 case BinOpComma: 131 // "!(a, b)" => "a, !b" 132 return Expr{Loc: expr.Loc, Data: &EBinary{Op: BinOpComma, Left: e.Left, Right: Not(e.Right)}}, true 133 } 134 } 135 136 return Expr{}, false 137 } 138 139 // This function intentionally avoids mutating the input AST so it can be 140 // called after the AST has been frozen (i.e. after parsing ends). 141 func MaybeSimplifyEqualityComparison(loc logger.Loc, e *EBinary, unsupportedFeatures compat.JSFeature) (Expr, bool) { 142 value, primitive := e.Left, e.Right 143 144 // Detect when the primitive comes first and flip the order of our checks 145 if IsPrimitiveLiteral(value.Data) { 146 value, primitive = primitive, value 147 } 148 149 // "!x === true" => "!x" 150 // "!x === false" => "!!x" 151 // "!x !== true" => "!!x" 152 // "!x !== false" => "!x" 153 if boolean, ok := primitive.Data.(*EBoolean); ok && KnownPrimitiveType(value.Data) == PrimitiveBoolean { 154 if boolean.Value == (e.Op == BinOpLooseNe || e.Op == BinOpStrictNe) { 155 return Not(value), true 156 } else { 157 return value, true 158 } 159 } 160 161 // "typeof x != 'undefined'" => "typeof x < 'u'" 162 // "typeof x == 'undefined'" => "typeof x > 'u'" 163 if !unsupportedFeatures.Has(compat.TypeofExoticObjectIsObject) { 164 // Only do this optimization if we know that the "typeof" operator won't 165 // return something random. The only case of this happening was Internet 166 // Explorer returning "unknown" for some objects, which messes with this 167 // optimization. So we don't do this when targeting Internet Explorer. 168 if typeof, ok := value.Data.(*EUnary); ok && typeof.Op == UnOpTypeof { 169 if str, ok := primitive.Data.(*EString); ok && helpers.UTF16EqualsString(str.Value, "undefined") { 170 flip := value == e.Right 171 op := BinOpLt 172 if (e.Op == BinOpLooseEq || e.Op == BinOpStrictEq) != flip { 173 op = BinOpGt 174 } 175 primitive.Data = &EString{Value: []uint16{'u'}} 176 if flip { 177 value, primitive = primitive, value 178 } 179 return Expr{Loc: loc, Data: &EBinary{Op: op, Left: value, Right: primitive}}, true 180 } 181 } 182 } 183 184 return Expr{}, false 185 } 186 187 func IsSymbolInstance(data E) bool { 188 switch e := data.(type) { 189 case *EDot: 190 return e.IsSymbolInstance 191 192 case *EIndex: 193 return e.IsSymbolInstance 194 } 195 return false 196 } 197 198 func IsPrimitiveLiteral(data E) bool { 199 switch e := data.(type) { 200 case *EAnnotation: 201 return IsPrimitiveLiteral(e.Value.Data) 202 203 case *EInlinedEnum: 204 return IsPrimitiveLiteral(e.Value.Data) 205 206 case *ENull, *EUndefined, *EString, *EBoolean, *ENumber, *EBigInt: 207 return true 208 } 209 return false 210 } 211 212 type PrimitiveType uint8 213 214 const ( 215 PrimitiveUnknown PrimitiveType = iota 216 PrimitiveMixed 217 PrimitiveNull 218 PrimitiveUndefined 219 PrimitiveBoolean 220 PrimitiveNumber 221 PrimitiveString 222 PrimitiveBigInt 223 ) 224 225 // This can be used when the returned type is either one or the other 226 func MergedKnownPrimitiveTypes(a Expr, b Expr) PrimitiveType { 227 x := KnownPrimitiveType(a.Data) 228 if x == PrimitiveUnknown { 229 return PrimitiveUnknown 230 } 231 232 y := KnownPrimitiveType(b.Data) 233 if y == PrimitiveUnknown { 234 return PrimitiveUnknown 235 } 236 237 if x == y { 238 return x 239 } 240 return PrimitiveMixed // Definitely some kind of primitive 241 } 242 243 // Note: This function does not say whether the expression is side-effect free 244 // or not. For example, the expression "++x" always returns a primitive. 245 func KnownPrimitiveType(expr E) PrimitiveType { 246 switch e := expr.(type) { 247 case *EAnnotation: 248 return KnownPrimitiveType(e.Value.Data) 249 250 case *EInlinedEnum: 251 return KnownPrimitiveType(e.Value.Data) 252 253 case *ENull: 254 return PrimitiveNull 255 256 case *EUndefined: 257 return PrimitiveUndefined 258 259 case *EBoolean: 260 return PrimitiveBoolean 261 262 case *ENumber: 263 return PrimitiveNumber 264 265 case *EString: 266 return PrimitiveString 267 268 case *EBigInt: 269 return PrimitiveBigInt 270 271 case *ETemplate: 272 if e.TagOrNil.Data == nil { 273 return PrimitiveString 274 } 275 276 case *EIf: 277 return MergedKnownPrimitiveTypes(e.Yes, e.No) 278 279 case *EUnary: 280 switch e.Op { 281 case UnOpVoid: 282 return PrimitiveUndefined 283 284 case UnOpTypeof: 285 return PrimitiveString 286 287 case UnOpNot, UnOpDelete: 288 return PrimitiveBoolean 289 290 case UnOpPos: 291 return PrimitiveNumber // Cannot be bigint because that throws an exception 292 293 case UnOpNeg, UnOpCpl: 294 value := KnownPrimitiveType(e.Value.Data) 295 if value == PrimitiveBigInt { 296 return PrimitiveBigInt 297 } 298 if value != PrimitiveUnknown && value != PrimitiveMixed { 299 return PrimitiveNumber 300 } 301 return PrimitiveMixed // Can be number or bigint 302 303 case UnOpPreDec, UnOpPreInc, UnOpPostDec, UnOpPostInc: 304 return PrimitiveMixed // Can be number or bigint 305 } 306 307 case *EBinary: 308 switch e.Op { 309 case BinOpStrictEq, BinOpStrictNe, BinOpLooseEq, BinOpLooseNe, 310 BinOpLt, BinOpGt, BinOpLe, BinOpGe, 311 BinOpInstanceof, BinOpIn: 312 return PrimitiveBoolean 313 314 case BinOpLogicalOr, BinOpLogicalAnd: 315 return MergedKnownPrimitiveTypes(e.Left, e.Right) 316 317 case BinOpNullishCoalescing: 318 left := KnownPrimitiveType(e.Left.Data) 319 right := KnownPrimitiveType(e.Right.Data) 320 if left == PrimitiveNull || left == PrimitiveUndefined { 321 return right 322 } 323 if left != PrimitiveUnknown { 324 if left != PrimitiveMixed { 325 return left // Definitely not null or undefined 326 } 327 if right != PrimitiveUnknown { 328 return PrimitiveMixed // Definitely some kind of primitive 329 } 330 } 331 332 case BinOpAdd: 333 left := KnownPrimitiveType(e.Left.Data) 334 right := KnownPrimitiveType(e.Right.Data) 335 if left == PrimitiveString || right == PrimitiveString { 336 return PrimitiveString 337 } 338 if left == PrimitiveBigInt && right == PrimitiveBigInt { 339 return PrimitiveBigInt 340 } 341 if left != PrimitiveUnknown && left != PrimitiveMixed && left != PrimitiveBigInt && 342 right != PrimitiveUnknown && right != PrimitiveMixed && right != PrimitiveBigInt { 343 return PrimitiveNumber 344 } 345 return PrimitiveMixed // Can be number or bigint or string (or an exception) 346 347 case BinOpAddAssign: 348 right := KnownPrimitiveType(e.Right.Data) 349 if right == PrimitiveString { 350 return PrimitiveString 351 } 352 return PrimitiveMixed // Can be number or bigint or string (or an exception) 353 354 case 355 BinOpSub, BinOpSubAssign, 356 BinOpMul, BinOpMulAssign, 357 BinOpDiv, BinOpDivAssign, 358 BinOpRem, BinOpRemAssign, 359 BinOpPow, BinOpPowAssign, 360 BinOpBitwiseAnd, BinOpBitwiseAndAssign, 361 BinOpBitwiseOr, BinOpBitwiseOrAssign, 362 BinOpBitwiseXor, BinOpBitwiseXorAssign, 363 BinOpShl, BinOpShlAssign, 364 BinOpShr, BinOpShrAssign, 365 BinOpUShr, BinOpUShrAssign: 366 return PrimitiveMixed // Can be number or bigint (or an exception) 367 368 case BinOpAssign, BinOpComma: 369 return KnownPrimitiveType(e.Right.Data) 370 } 371 } 372 373 return PrimitiveUnknown 374 } 375 376 func CanChangeStrictToLoose(a Expr, b Expr) bool { 377 x := KnownPrimitiveType(a.Data) 378 y := KnownPrimitiveType(b.Data) 379 return x == y && x != PrimitiveUnknown && x != PrimitiveMixed 380 } 381 382 // Returns true if the result of the "typeof" operator on this expression is 383 // statically determined and this expression has no side effects (i.e. can be 384 // removed without consequence). 385 func TypeofWithoutSideEffects(data E) (string, bool) { 386 switch e := data.(type) { 387 case *EAnnotation: 388 if e.Flags.Has(CanBeRemovedIfUnusedFlag) { 389 return TypeofWithoutSideEffects(e.Value.Data) 390 } 391 392 case *EInlinedEnum: 393 return TypeofWithoutSideEffects(e.Value.Data) 394 395 case *ENull: 396 return "object", true 397 398 case *EUndefined: 399 return "undefined", true 400 401 case *EBoolean: 402 return "boolean", true 403 404 case *ENumber: 405 return "number", true 406 407 case *EBigInt: 408 return "bigint", true 409 410 case *EString: 411 return "string", true 412 413 case *EFunction, *EArrow: 414 return "function", true 415 } 416 417 return "", false 418 } 419 420 // The goal of this function is to "rotate" the AST if it's possible to use the 421 // left-associative property of the operator to avoid unnecessary parentheses. 422 // 423 // When using this, make absolutely sure that the operator is actually 424 // associative. For example, the "+" operator is not associative for 425 // floating-point numbers. 426 // 427 // This function intentionally avoids mutating the input AST so it can be 428 // called after the AST has been frozen (i.e. after parsing ends). 429 func JoinWithLeftAssociativeOp(op OpCode, a Expr, b Expr) Expr { 430 // "(a, b) op c" => "a, b op c" 431 if comma, ok := a.Data.(*EBinary); ok && comma.Op == BinOpComma { 432 // Don't mutate the original AST 433 clone := *comma 434 clone.Right = JoinWithLeftAssociativeOp(op, clone.Right, b) 435 return Expr{Loc: a.Loc, Data: &clone} 436 } 437 438 // "a op (b op c)" => "(a op b) op c" 439 // "a op (b op (c op d))" => "((a op b) op c) op d" 440 for { 441 if binary, ok := b.Data.(*EBinary); ok && binary.Op == op { 442 a = JoinWithLeftAssociativeOp(op, a, binary.Left) 443 b = binary.Right 444 } else { 445 break 446 } 447 } 448 449 // "a op b" => "a op b" 450 // "(a op b) op c" => "(a op b) op c" 451 return Expr{Loc: a.Loc, Data: &EBinary{Op: op, Left: a, Right: b}} 452 } 453 454 func JoinWithComma(a Expr, b Expr) Expr { 455 if a.Data == nil { 456 return b 457 } 458 if b.Data == nil { 459 return a 460 } 461 return Expr{Loc: a.Loc, Data: &EBinary{Op: BinOpComma, Left: a, Right: b}} 462 } 463 464 func JoinAllWithComma(all []Expr) (result Expr) { 465 for _, value := range all { 466 result = JoinWithComma(result, value) 467 } 468 return 469 } 470 471 func ConvertBindingToExpr(binding Binding, wrapIdentifier func(logger.Loc, ast.Ref) Expr) Expr { 472 loc := binding.Loc 473 474 switch b := binding.Data.(type) { 475 case *BMissing: 476 return Expr{Loc: loc, Data: &EMissing{}} 477 478 case *BIdentifier: 479 if wrapIdentifier != nil { 480 return wrapIdentifier(loc, b.Ref) 481 } 482 return Expr{Loc: loc, Data: &EIdentifier{Ref: b.Ref}} 483 484 case *BArray: 485 exprs := make([]Expr, len(b.Items)) 486 for i, item := range b.Items { 487 expr := ConvertBindingToExpr(item.Binding, wrapIdentifier) 488 if b.HasSpread && i+1 == len(b.Items) { 489 expr = Expr{Loc: expr.Loc, Data: &ESpread{Value: expr}} 490 } else if item.DefaultValueOrNil.Data != nil { 491 expr = Assign(expr, item.DefaultValueOrNil) 492 } 493 exprs[i] = expr 494 } 495 return Expr{Loc: loc, Data: &EArray{ 496 Items: exprs, 497 IsSingleLine: b.IsSingleLine, 498 }} 499 500 case *BObject: 501 properties := make([]Property, len(b.Properties)) 502 for i, property := range b.Properties { 503 value := ConvertBindingToExpr(property.Value, wrapIdentifier) 504 kind := PropertyField 505 if property.IsSpread { 506 kind = PropertySpread 507 } 508 var flags PropertyFlags 509 if property.IsComputed { 510 flags |= PropertyIsComputed 511 } 512 properties[i] = Property{ 513 Kind: kind, 514 Flags: flags, 515 Key: property.Key, 516 ValueOrNil: value, 517 InitializerOrNil: property.DefaultValueOrNil, 518 } 519 } 520 return Expr{Loc: loc, Data: &EObject{ 521 Properties: properties, 522 IsSingleLine: b.IsSingleLine, 523 }} 524 525 default: 526 panic("Internal error") 527 } 528 } 529 530 // This will return a nil expression if the expression can be totally removed. 531 // 532 // This function intentionally avoids mutating the input AST so it can be 533 // called after the AST has been frozen (i.e. after parsing ends). 534 func (ctx HelperContext) SimplifyUnusedExpr(expr Expr, unsupportedFeatures compat.JSFeature) Expr { 535 switch e := expr.Data.(type) { 536 case *EAnnotation: 537 if e.Flags.Has(CanBeRemovedIfUnusedFlag) { 538 return Expr{} 539 } 540 541 case *EInlinedEnum: 542 return ctx.SimplifyUnusedExpr(e.Value, unsupportedFeatures) 543 544 case *ENull, *EUndefined, *EMissing, *EBoolean, *ENumber, *EBigInt, 545 *EString, *EThis, *ERegExp, *EFunction, *EArrow, *EImportMeta: 546 return Expr{} 547 548 case *EDot: 549 if e.CanBeRemovedIfUnused { 550 return Expr{} 551 } 552 553 case *EIdentifier: 554 if e.MustKeepDueToWithStmt { 555 break 556 } 557 if e.CanBeRemovedIfUnused || !ctx.isUnbound(e.Ref) { 558 return Expr{} 559 } 560 561 case *ETemplate: 562 if e.TagOrNil.Data == nil { 563 var comma Expr 564 var templateLoc logger.Loc 565 var template *ETemplate 566 for _, part := range e.Parts { 567 // If we know this value is some kind of primitive, then we know that 568 // "ToString" has no side effects and can be avoided. 569 if KnownPrimitiveType(part.Value.Data) != PrimitiveUnknown { 570 if template != nil { 571 comma = JoinWithComma(comma, Expr{Loc: templateLoc, Data: template}) 572 template = nil 573 } 574 comma = JoinWithComma(comma, ctx.SimplifyUnusedExpr(part.Value, unsupportedFeatures)) 575 continue 576 } 577 578 // Make sure "ToString" is still evaluated on the value. We can't use 579 // string addition here because that may evaluate "ValueOf" instead. 580 if template == nil { 581 template = &ETemplate{} 582 templateLoc = part.Value.Loc 583 } 584 template.Parts = append(template.Parts, TemplatePart{Value: part.Value}) 585 } 586 if template != nil { 587 comma = JoinWithComma(comma, Expr{Loc: templateLoc, Data: template}) 588 } 589 return comma 590 } else if e.CanBeUnwrappedIfUnused { 591 // If the function call was annotated as being able to be removed if the 592 // result is unused, then we can remove it and just keep the arguments. 593 // Note that there are no implicit "ToString" operations for tagged 594 // template literals. 595 var comma Expr 596 for _, part := range e.Parts { 597 comma = JoinWithComma(comma, ctx.SimplifyUnusedExpr(part.Value, unsupportedFeatures)) 598 } 599 return comma 600 } 601 602 case *EArray: 603 // Arrays with "..." spread expressions can't be unwrapped because the 604 // "..." triggers code evaluation via iterators. In that case, just trim 605 // the other items instead and leave the array expression there. 606 for _, spread := range e.Items { 607 if _, ok := spread.Data.(*ESpread); ok { 608 items := make([]Expr, 0, len(e.Items)) 609 for _, item := range e.Items { 610 item = ctx.SimplifyUnusedExpr(item, unsupportedFeatures) 611 if item.Data != nil { 612 items = append(items, item) 613 } 614 } 615 616 // Don't mutate the original AST 617 clone := *e 618 clone.Items = items 619 return Expr{Loc: expr.Loc, Data: &clone} 620 } 621 } 622 623 // Otherwise, the array can be completely removed. We only need to keep any 624 // array items with side effects. Apply this simplification recursively. 625 var result Expr 626 for _, item := range e.Items { 627 result = JoinWithComma(result, ctx.SimplifyUnusedExpr(item, unsupportedFeatures)) 628 } 629 return result 630 631 case *EObject: 632 // Objects with "..." spread expressions can't be unwrapped because the 633 // "..." triggers code evaluation via getters. In that case, just trim 634 // the other items instead and leave the object expression there. 635 for _, spread := range e.Properties { 636 if spread.Kind == PropertySpread { 637 properties := make([]Property, 0, len(e.Properties)) 638 for _, property := range e.Properties { 639 // Spread properties must always be evaluated 640 if property.Kind != PropertySpread { 641 value := ctx.SimplifyUnusedExpr(property.ValueOrNil, unsupportedFeatures) 642 if value.Data != nil { 643 // Keep the value 644 property.ValueOrNil = value 645 } else if !property.Flags.Has(PropertyIsComputed) { 646 // Skip this property if the key doesn't need to be computed 647 continue 648 } else { 649 // Replace values without side effects with "0" because it's short 650 property.ValueOrNil.Data = &ENumber{} 651 } 652 } 653 properties = append(properties, property) 654 } 655 656 // Don't mutate the original AST 657 clone := *e 658 clone.Properties = properties 659 return Expr{Loc: expr.Loc, Data: &clone} 660 } 661 } 662 663 // Otherwise, the object can be completely removed. We only need to keep any 664 // object properties with side effects. Apply this simplification recursively. 665 var result Expr 666 for _, property := range e.Properties { 667 if property.Flags.Has(PropertyIsComputed) { 668 // Make sure "ToString" is still evaluated on the key 669 result = JoinWithComma(result, Expr{Loc: property.Key.Loc, Data: &EBinary{ 670 Op: BinOpAdd, 671 Left: property.Key, 672 Right: Expr{Loc: property.Key.Loc, Data: &EString{}}, 673 }}) 674 } 675 result = JoinWithComma(result, ctx.SimplifyUnusedExpr(property.ValueOrNil, unsupportedFeatures)) 676 } 677 return result 678 679 case *EIf: 680 yes := ctx.SimplifyUnusedExpr(e.Yes, unsupportedFeatures) 681 no := ctx.SimplifyUnusedExpr(e.No, unsupportedFeatures) 682 683 // "foo() ? 1 : 2" => "foo()" 684 if yes.Data == nil && no.Data == nil { 685 return ctx.SimplifyUnusedExpr(e.Test, unsupportedFeatures) 686 } 687 688 // "foo() ? 1 : bar()" => "foo() || bar()" 689 if yes.Data == nil { 690 return JoinWithLeftAssociativeOp(BinOpLogicalOr, e.Test, no) 691 } 692 693 // "foo() ? bar() : 2" => "foo() && bar()" 694 if no.Data == nil { 695 return JoinWithLeftAssociativeOp(BinOpLogicalAnd, e.Test, yes) 696 } 697 698 if yes != e.Yes || no != e.No { 699 return Expr{Loc: expr.Loc, Data: &EIf{Test: e.Test, Yes: yes, No: no}} 700 } 701 702 case *EUnary: 703 switch e.Op { 704 // These operators must not have any type conversions that can execute code 705 // such as "toString" or "valueOf". They must also never throw any exceptions. 706 case UnOpVoid, UnOpNot: 707 return ctx.SimplifyUnusedExpr(e.Value, unsupportedFeatures) 708 709 case UnOpTypeof: 710 if _, ok := e.Value.Data.(*EIdentifier); ok && e.WasOriginallyTypeofIdentifier { 711 // "typeof x" must not be transformed into if "x" since doing so could 712 // cause an exception to be thrown. Instead we can just remove it since 713 // "typeof x" is special-cased in the standard to never throw. 714 return Expr{} 715 } 716 return ctx.SimplifyUnusedExpr(e.Value, unsupportedFeatures) 717 } 718 719 case *EBinary: 720 left := e.Left 721 right := e.Right 722 723 switch e.Op { 724 // These operators must not have any type conversions that can execute code 725 // such as "toString" or "valueOf". They must also never throw any exceptions. 726 case BinOpStrictEq, BinOpStrictNe, BinOpComma: 727 return JoinWithComma(ctx.SimplifyUnusedExpr(left, unsupportedFeatures), ctx.SimplifyUnusedExpr(right, unsupportedFeatures)) 728 729 // We can simplify "==" and "!=" even though they can call "toString" and/or 730 // "valueOf" if we can statically determine that the types of both sides are 731 // primitives. In that case there won't be any chance for user-defined 732 // "toString" and/or "valueOf" to be called. 733 case BinOpLooseEq, BinOpLooseNe: 734 if MergedKnownPrimitiveTypes(left, right) != PrimitiveUnknown { 735 return JoinWithComma(ctx.SimplifyUnusedExpr(left, unsupportedFeatures), ctx.SimplifyUnusedExpr(right, unsupportedFeatures)) 736 } 737 738 case BinOpLogicalAnd, BinOpLogicalOr, BinOpNullishCoalescing: 739 // If this is a boolean logical operation and the result is unused, then 740 // we know the left operand will only be used for its boolean value and 741 // can be simplified under that assumption 742 if e.Op != BinOpNullishCoalescing { 743 left = ctx.SimplifyBooleanExpr(left) 744 } 745 746 // Preserve short-circuit behavior: the left expression is only unused if 747 // the right expression can be completely removed. Otherwise, the left 748 // expression is important for the branch. 749 right = ctx.SimplifyUnusedExpr(right, unsupportedFeatures) 750 if right.Data == nil { 751 return ctx.SimplifyUnusedExpr(left, unsupportedFeatures) 752 } 753 754 // Try to take advantage of the optional chain operator to shorten code 755 if !unsupportedFeatures.Has(compat.OptionalChain) { 756 if binary, ok := left.Data.(*EBinary); ok { 757 // "a != null && a.b()" => "a?.b()" 758 // "a == null || a.b()" => "a?.b()" 759 if (binary.Op == BinOpLooseNe && e.Op == BinOpLogicalAnd) || (binary.Op == BinOpLooseEq && e.Op == BinOpLogicalOr) { 760 var test Expr 761 if _, ok := binary.Right.Data.(*ENull); ok { 762 test = binary.Left 763 } else if _, ok := binary.Left.Data.(*ENull); ok { 764 test = binary.Right 765 } 766 767 // Note: Technically unbound identifiers can refer to a getter on 768 // the global object and that getter can have side effects that can 769 // be observed if we run that getter once instead of twice. But this 770 // seems like terrible coding practice and very unlikely to come up 771 // in real software, so we deliberately ignore this possibility and 772 // optimize for size instead of for this obscure edge case. 773 // 774 // If this is ever changed, then we must also pessimize the lowering 775 // of "foo?.bar" to save the value of "foo" to ensure that it's only 776 // evaluated once. Specifically "foo?.bar" would have to expand to: 777 // 778 // var _a; 779 // (_a = foo) == null ? void 0 : _a.bar; 780 // 781 // instead of: 782 // 783 // foo == null ? void 0 : foo.bar; 784 // 785 // Babel does the first one while TypeScript does the second one. 786 // Since TypeScript doesn't handle this extreme edge case and 787 // TypeScript is very widely used, I think it's fine for us to not 788 // handle this edge case either. 789 if id, ok := test.Data.(*EIdentifier); ok && !id.MustKeepDueToWithStmt && TryToInsertOptionalChain(test, right) { 790 return right 791 } 792 } 793 } 794 } 795 796 case BinOpAdd: 797 if result, isStringAddition := simplifyUnusedStringAdditionChain(expr); isStringAddition { 798 return result 799 } 800 } 801 802 if left != e.Left || right != e.Right { 803 return Expr{Loc: expr.Loc, Data: &EBinary{Op: e.Op, Left: left, Right: right}} 804 } 805 806 case *ECall: 807 // A call that has been marked "__PURE__" can be removed if all arguments 808 // can be removed. The annotation causes us to ignore the target. 809 if e.CanBeUnwrappedIfUnused { 810 var result Expr 811 for _, arg := range e.Args { 812 if _, ok := arg.Data.(*ESpread); ok { 813 arg.Data = &EArray{Items: []Expr{arg}, IsSingleLine: true} 814 } 815 result = JoinWithComma(result, ctx.SimplifyUnusedExpr(arg, unsupportedFeatures)) 816 } 817 return result 818 } 819 820 // Attempt to shorten IIFEs 821 if len(e.Args) == 0 { 822 switch target := e.Target.Data.(type) { 823 case *EFunction: 824 if len(target.Fn.Args) != 0 { 825 break 826 } 827 828 // Just delete "(function() {})()" completely 829 if len(target.Fn.Body.Block.Stmts) == 0 { 830 return Expr{} 831 } 832 833 case *EArrow: 834 if len(target.Args) != 0 { 835 break 836 } 837 838 // Just delete "(() => {})()" completely 839 if len(target.Body.Block.Stmts) == 0 { 840 return Expr{} 841 } 842 843 if len(target.Body.Block.Stmts) == 1 { 844 switch s := target.Body.Block.Stmts[0].Data.(type) { 845 case *SExpr: 846 if !target.IsAsync { 847 // Replace "(() => { foo() })()" with "foo()" 848 return s.Value 849 } else { 850 // Replace "(async () => { foo() })()" with "(async () => foo())()" 851 clone := *target 852 clone.Body.Block.Stmts[0].Data = &SReturn{ValueOrNil: s.Value} 853 clone.PreferExpr = true 854 return Expr{Loc: expr.Loc, Data: &ECall{Target: Expr{Loc: e.Target.Loc, Data: &clone}}} 855 } 856 857 case *SReturn: 858 if !target.IsAsync { 859 // Replace "(() => foo())()" with "foo()" 860 return s.ValueOrNil 861 } 862 } 863 } 864 } 865 } 866 867 case *ENew: 868 // A constructor call that has been marked "__PURE__" can be removed if all 869 // arguments can be removed. The annotation causes us to ignore the target. 870 if e.CanBeUnwrappedIfUnused { 871 var result Expr 872 for _, arg := range e.Args { 873 if _, ok := arg.Data.(*ESpread); ok { 874 arg.Data = &EArray{Items: []Expr{arg}, IsSingleLine: true} 875 } 876 result = JoinWithComma(result, ctx.SimplifyUnusedExpr(arg, unsupportedFeatures)) 877 } 878 return result 879 } 880 } 881 882 return expr 883 } 884 885 // This function intentionally avoids mutating the input AST so it can be 886 // called after the AST has been frozen (i.e. after parsing ends). 887 func simplifyUnusedStringAdditionChain(expr Expr) (Expr, bool) { 888 switch e := expr.Data.(type) { 889 case *EString: 890 // "'x' + y" => "'' + y" 891 return Expr{Loc: expr.Loc, Data: &EString{}}, true 892 893 case *EBinary: 894 if e.Op == BinOpAdd { 895 left, leftIsStringAddition := simplifyUnusedStringAdditionChain(e.Left) 896 897 if right, rightIsString := e.Right.Data.(*EString); rightIsString { 898 // "('' + x) + 'y'" => "'' + x" 899 if leftIsStringAddition { 900 return left, true 901 } 902 903 // "x + 'y'" => "x + ''" 904 if !leftIsStringAddition && len(right.Value) > 0 { 905 return Expr{Loc: expr.Loc, Data: &EBinary{ 906 Op: BinOpAdd, 907 Left: left, 908 Right: Expr{Loc: e.Right.Loc, Data: &EString{}}, 909 }}, true 910 } 911 } 912 913 // Don't mutate the original AST 914 if left != e.Left { 915 expr.Data = &EBinary{Op: BinOpAdd, Left: left, Right: e.Right} 916 } 917 918 return expr, leftIsStringAddition 919 } 920 } 921 922 return expr, false 923 } 924 925 func ToInt32(f float64) int32 { 926 // The easy way 927 i := int32(f) 928 if float64(i) == f { 929 return i 930 } 931 932 // Special-case non-finite numbers (casting them is unspecified behavior in Go) 933 if math.IsNaN(f) || math.IsInf(f, 0) { 934 return 0 935 } 936 937 // The hard way 938 i = int32(uint32(math.Mod(math.Abs(f), 4294967296))) 939 if math.Signbit(f) { 940 return -i 941 } 942 return i 943 } 944 945 func ToUint32(f float64) uint32 { 946 return uint32(ToInt32(f)) 947 } 948 949 func isInt32OrUint32(data E) bool { 950 switch e := data.(type) { 951 case *EUnary: 952 return e.Op == UnOpCpl 953 954 case *EBinary: 955 switch e.Op { 956 case BinOpBitwiseAnd, BinOpBitwiseOr, BinOpBitwiseXor, BinOpShl, BinOpShr, BinOpUShr: 957 return true 958 959 case BinOpLogicalOr, BinOpLogicalAnd: 960 return isInt32OrUint32(e.Left.Data) && isInt32OrUint32(e.Right.Data) 961 } 962 963 case *EIf: 964 return isInt32OrUint32(e.Yes.Data) && isInt32OrUint32(e.No.Data) 965 } 966 return false 967 } 968 969 func ToNumberWithoutSideEffects(data E) (float64, bool) { 970 switch e := data.(type) { 971 case *EAnnotation: 972 return ToNumberWithoutSideEffects(e.Value.Data) 973 974 case *EInlinedEnum: 975 return ToNumberWithoutSideEffects(e.Value.Data) 976 977 case *ENull: 978 return 0, true 979 980 case *EUndefined, *ERegExp: 981 return math.NaN(), true 982 983 case *EArray: 984 if len(e.Items) == 0 { 985 // "+[]" => "0" 986 return 0, true 987 } 988 989 case *EObject: 990 if len(e.Properties) == 0 { 991 // "+{}" => "NaN" 992 return math.NaN(), true 993 } 994 995 case *EBoolean: 996 if e.Value { 997 return 1, true 998 } else { 999 return 0, true 1000 } 1001 1002 case *ENumber: 1003 return e.Value, true 1004 1005 case *EString: 1006 // "+''" => "0" 1007 if len(e.Value) == 0 { 1008 return 0, true 1009 } 1010 1011 // "+'1'" => "1" 1012 if num, ok := StringToEquivalentNumberValue(e.Value); ok { 1013 return num, true 1014 } 1015 } 1016 1017 return 0, false 1018 } 1019 1020 func ToStringWithoutSideEffects(data E) (string, bool) { 1021 switch e := data.(type) { 1022 case *ENull: 1023 return "null", true 1024 1025 case *EUndefined: 1026 return "undefined", true 1027 1028 case *EBoolean: 1029 if e.Value { 1030 return "true", true 1031 } else { 1032 return "false", true 1033 } 1034 1035 case *EBigInt: 1036 // Only do this if there is no radix 1037 if len(e.Value) < 2 || e.Value[0] != '0' { 1038 return e.Value, true 1039 } 1040 1041 case *ENumber: 1042 if str, ok := TryToStringOnNumberSafely(e.Value, 10); ok { 1043 return str, true 1044 } 1045 1046 case *ERegExp: 1047 return e.Value, true 1048 1049 case *EDot: 1050 // This is dumb but some JavaScript obfuscators use this to generate string literals 1051 if e.Name == "constructor" { 1052 switch e.Target.Data.(type) { 1053 case *EString: 1054 return "function String() { [native code] }", true 1055 1056 case *ERegExp: 1057 return "function RegExp() { [native code] }", true 1058 } 1059 } 1060 } 1061 1062 return "", false 1063 } 1064 1065 func extractNumericValue(data E) (float64, bool) { 1066 switch e := data.(type) { 1067 case *EAnnotation: 1068 return extractNumericValue(e.Value.Data) 1069 1070 case *EInlinedEnum: 1071 return extractNumericValue(e.Value.Data) 1072 1073 case *ENumber: 1074 return e.Value, true 1075 } 1076 1077 return 0, false 1078 } 1079 1080 func extractNumericValues(left Expr, right Expr) (float64, float64, bool) { 1081 if a, ok := extractNumericValue(left.Data); ok { 1082 if b, ok := extractNumericValue(right.Data); ok { 1083 return a, b, true 1084 } 1085 } 1086 return 0, 0, false 1087 } 1088 1089 func extractStringValue(data E) ([]uint16, bool) { 1090 switch e := data.(type) { 1091 case *EAnnotation: 1092 return extractStringValue(e.Value.Data) 1093 1094 case *EInlinedEnum: 1095 return extractStringValue(e.Value.Data) 1096 1097 case *EString: 1098 return e.Value, true 1099 } 1100 1101 return nil, false 1102 } 1103 1104 func extractStringValues(left Expr, right Expr) ([]uint16, []uint16, bool) { 1105 if a, ok := extractStringValue(left.Data); ok { 1106 if b, ok := extractStringValue(right.Data); ok { 1107 return a, b, true 1108 } 1109 } 1110 return nil, nil, false 1111 } 1112 1113 func stringCompareUCS2(a []uint16, b []uint16) int { 1114 var n int 1115 if len(a) < len(b) { 1116 n = len(a) 1117 } else { 1118 n = len(b) 1119 } 1120 for i := 0; i < n; i++ { 1121 if delta := int(a[i]) - int(b[i]); delta != 0 { 1122 return delta 1123 } 1124 } 1125 return len(a) - len(b) 1126 } 1127 1128 func approximatePrintedIntCharCount(intValue float64) int { 1129 count := 1 + (int)(math.Max(0, math.Floor(math.Log10(math.Abs(intValue))))) 1130 if intValue < 0 { 1131 count++ 1132 } 1133 return count 1134 } 1135 1136 func ShouldFoldBinaryOperatorWhenMinifying(binary *EBinary) bool { 1137 switch binary.Op { 1138 case 1139 // Equality tests should always result in smaller code when folded 1140 BinOpLooseEq, 1141 BinOpLooseNe, 1142 BinOpStrictEq, 1143 BinOpStrictNe, 1144 1145 // Minification always folds right signed shift operations since they are 1146 // unlikely to result in larger output. Note: ">>>" could result in 1147 // bigger output such as "-1 >>> 0" becoming "4294967295". 1148 BinOpShr, 1149 1150 // Minification always folds the following bitwise operations since they 1151 // are unlikely to result in larger output. 1152 BinOpBitwiseAnd, 1153 BinOpBitwiseOr, 1154 BinOpBitwiseXor, 1155 BinOpLt, 1156 BinOpGt, 1157 BinOpLe, 1158 BinOpGe: 1159 return true 1160 1161 case BinOpAdd: 1162 // Addition of small-ish integers can definitely be folded without issues 1163 // "1 + 2" => "3" 1164 if left, right, ok := extractNumericValues(binary.Left, binary.Right); ok && 1165 left == math.Trunc(left) && math.Abs(left) <= 0xFFFF_FFFF && 1166 right == math.Trunc(right) && math.Abs(right) <= 0xFFFF_FFFF { 1167 return true 1168 } 1169 1170 // String addition should pretty much always be more compact when folded 1171 if _, _, ok := extractStringValues(binary.Left, binary.Right); ok { 1172 return true 1173 } 1174 1175 case BinOpSub: 1176 // Subtraction of small-ish integers can definitely be folded without issues 1177 // "3 - 1" => "2" 1178 if left, right, ok := extractNumericValues(binary.Left, binary.Right); ok && 1179 left == math.Trunc(left) && math.Abs(left) <= 0xFFFF_FFFF && 1180 right == math.Trunc(right) && math.Abs(right) <= 0xFFFF_FFFF { 1181 return true 1182 } 1183 1184 case BinOpDiv: 1185 // "0/0" => "NaN" 1186 // "1/0" => "Infinity" 1187 // "1/-0" => "-Infinity" 1188 if _, right, ok := extractNumericValues(binary.Left, binary.Right); ok && right == 0 { 1189 return true 1190 } 1191 1192 case BinOpShl: 1193 // "1 << 3" => "8" 1194 // "1 << 24" => "1 << 24" (since "1<<24" is shorter than "16777216") 1195 if left, right, ok := extractNumericValues(binary.Left, binary.Right); ok { 1196 leftLen := approximatePrintedIntCharCount(left) 1197 rightLen := approximatePrintedIntCharCount(right) 1198 resultLen := approximatePrintedIntCharCount(float64(ToInt32(left) << (ToUint32(right) & 31))) 1199 return resultLen <= leftLen+2+rightLen 1200 } 1201 1202 case BinOpUShr: 1203 // "10 >>> 1" => "5" 1204 // "-1 >>> 0" => "-1 >>> 0" (since "-1>>>0" is shorter than "4294967295") 1205 if left, right, ok := extractNumericValues(binary.Left, binary.Right); ok { 1206 leftLen := approximatePrintedIntCharCount(left) 1207 rightLen := approximatePrintedIntCharCount(right) 1208 resultLen := approximatePrintedIntCharCount(float64(ToUint32(left) >> (ToUint32(right) & 31))) 1209 return resultLen <= leftLen+3+rightLen 1210 } 1211 1212 case BinOpLogicalAnd, BinOpLogicalOr, BinOpNullishCoalescing: 1213 if IsPrimitiveLiteral(binary.Left.Data) { 1214 return true 1215 } 1216 } 1217 return false 1218 } 1219 1220 // This function intentionally avoids mutating the input AST so it can be 1221 // called after the AST has been frozen (i.e. after parsing ends). 1222 func FoldBinaryOperator(loc logger.Loc, e *EBinary) Expr { 1223 switch e.Op { 1224 case BinOpAdd: 1225 if left, right, ok := extractNumericValues(e.Left, e.Right); ok { 1226 return Expr{Loc: loc, Data: &ENumber{Value: left + right}} 1227 } 1228 if left, right, ok := extractStringValues(e.Left, e.Right); ok { 1229 return Expr{Loc: loc, Data: &EString{Value: joinStrings(left, right)}} 1230 } 1231 1232 case BinOpSub: 1233 if left, right, ok := extractNumericValues(e.Left, e.Right); ok { 1234 return Expr{Loc: loc, Data: &ENumber{Value: left - right}} 1235 } 1236 1237 case BinOpMul: 1238 if left, right, ok := extractNumericValues(e.Left, e.Right); ok { 1239 return Expr{Loc: loc, Data: &ENumber{Value: left * right}} 1240 } 1241 1242 case BinOpDiv: 1243 if left, right, ok := extractNumericValues(e.Left, e.Right); ok { 1244 return Expr{Loc: loc, Data: &ENumber{Value: left / right}} 1245 } 1246 1247 case BinOpRem: 1248 if left, right, ok := extractNumericValues(e.Left, e.Right); ok { 1249 return Expr{Loc: loc, Data: &ENumber{Value: math.Mod(left, right)}} 1250 } 1251 1252 case BinOpPow: 1253 if left, right, ok := extractNumericValues(e.Left, e.Right); ok { 1254 return Expr{Loc: loc, Data: &ENumber{Value: math.Pow(left, right)}} 1255 } 1256 1257 case BinOpShl: 1258 if left, right, ok := extractNumericValues(e.Left, e.Right); ok { 1259 return Expr{Loc: loc, Data: &ENumber{Value: float64(ToInt32(left) << (ToUint32(right) & 31))}} 1260 } 1261 1262 case BinOpShr: 1263 if left, right, ok := extractNumericValues(e.Left, e.Right); ok { 1264 return Expr{Loc: loc, Data: &ENumber{Value: float64(ToInt32(left) >> (ToUint32(right) & 31))}} 1265 } 1266 1267 case BinOpUShr: 1268 if left, right, ok := extractNumericValues(e.Left, e.Right); ok { 1269 return Expr{Loc: loc, Data: &ENumber{Value: float64(ToUint32(left) >> (ToUint32(right) & 31))}} 1270 } 1271 1272 case BinOpBitwiseAnd: 1273 if left, right, ok := extractNumericValues(e.Left, e.Right); ok { 1274 return Expr{Loc: loc, Data: &ENumber{Value: float64(ToInt32(left) & ToInt32(right))}} 1275 } 1276 1277 case BinOpBitwiseOr: 1278 if left, right, ok := extractNumericValues(e.Left, e.Right); ok { 1279 return Expr{Loc: loc, Data: &ENumber{Value: float64(ToInt32(left) | ToInt32(right))}} 1280 } 1281 1282 case BinOpBitwiseXor: 1283 if left, right, ok := extractNumericValues(e.Left, e.Right); ok { 1284 return Expr{Loc: loc, Data: &ENumber{Value: float64(ToInt32(left) ^ ToInt32(right))}} 1285 } 1286 1287 case BinOpLt: 1288 if left, right, ok := extractNumericValues(e.Left, e.Right); ok { 1289 return Expr{Loc: loc, Data: &EBoolean{Value: left < right}} 1290 } 1291 if left, right, ok := extractStringValues(e.Left, e.Right); ok { 1292 return Expr{Loc: loc, Data: &EBoolean{Value: stringCompareUCS2(left, right) < 0}} 1293 } 1294 1295 case BinOpGt: 1296 if left, right, ok := extractNumericValues(e.Left, e.Right); ok { 1297 return Expr{Loc: loc, Data: &EBoolean{Value: left > right}} 1298 } 1299 if left, right, ok := extractStringValues(e.Left, e.Right); ok { 1300 return Expr{Loc: loc, Data: &EBoolean{Value: stringCompareUCS2(left, right) > 0}} 1301 } 1302 1303 case BinOpLe: 1304 if left, right, ok := extractNumericValues(e.Left, e.Right); ok { 1305 return Expr{Loc: loc, Data: &EBoolean{Value: left <= right}} 1306 } 1307 if left, right, ok := extractStringValues(e.Left, e.Right); ok { 1308 return Expr{Loc: loc, Data: &EBoolean{Value: stringCompareUCS2(left, right) <= 0}} 1309 } 1310 1311 case BinOpGe: 1312 if left, right, ok := extractNumericValues(e.Left, e.Right); ok { 1313 return Expr{Loc: loc, Data: &EBoolean{Value: left >= right}} 1314 } 1315 if left, right, ok := extractStringValues(e.Left, e.Right); ok { 1316 return Expr{Loc: loc, Data: &EBoolean{Value: stringCompareUCS2(left, right) >= 0}} 1317 } 1318 1319 case BinOpLooseEq, BinOpStrictEq: 1320 if left, right, ok := extractNumericValues(e.Left, e.Right); ok { 1321 return Expr{Loc: loc, Data: &EBoolean{Value: left == right}} 1322 } 1323 if left, right, ok := extractStringValues(e.Left, e.Right); ok { 1324 return Expr{Loc: loc, Data: &EBoolean{Value: stringCompareUCS2(left, right) == 0}} 1325 } 1326 1327 case BinOpLooseNe, BinOpStrictNe: 1328 if left, right, ok := extractNumericValues(e.Left, e.Right); ok { 1329 return Expr{Loc: loc, Data: &EBoolean{Value: left != right}} 1330 } 1331 if left, right, ok := extractStringValues(e.Left, e.Right); ok { 1332 return Expr{Loc: loc, Data: &EBoolean{Value: stringCompareUCS2(left, right) != 0}} 1333 } 1334 1335 case BinOpLogicalAnd: 1336 if boolean, sideEffects, ok := ToBooleanWithSideEffects(e.Left.Data); ok { 1337 if !boolean { 1338 return e.Left 1339 } else if sideEffects == NoSideEffects { 1340 return e.Right 1341 } 1342 } 1343 1344 case BinOpLogicalOr: 1345 if boolean, sideEffects, ok := ToBooleanWithSideEffects(e.Left.Data); ok { 1346 if boolean { 1347 return e.Left 1348 } else if sideEffects == NoSideEffects { 1349 return e.Right 1350 } 1351 } 1352 1353 case BinOpNullishCoalescing: 1354 if isNullOrUndefined, sideEffects, ok := ToNullOrUndefinedWithSideEffects(e.Left.Data); ok { 1355 if !isNullOrUndefined { 1356 return e.Left 1357 } else if sideEffects == NoSideEffects { 1358 return e.Right 1359 } 1360 } 1361 } 1362 1363 return Expr{} 1364 } 1365 1366 func IsBinaryNullAndUndefined(left Expr, right Expr, op OpCode) (Expr, Expr, bool) { 1367 if a, ok := left.Data.(*EBinary); ok && a.Op == op { 1368 if b, ok := right.Data.(*EBinary); ok && b.Op == op { 1369 idA, eqA := a.Left, a.Right 1370 idB, eqB := b.Left, b.Right 1371 1372 // Detect when the identifier comes second and flip the order of our checks 1373 if _, ok := eqA.Data.(*EIdentifier); ok { 1374 idA, eqA = eqA, idA 1375 } 1376 if _, ok := eqB.Data.(*EIdentifier); ok { 1377 idB, eqB = eqB, idB 1378 } 1379 1380 if idA, ok := idA.Data.(*EIdentifier); ok { 1381 if idB, ok := idB.Data.(*EIdentifier); ok && idA.Ref == idB.Ref { 1382 // "a === null || a === void 0" 1383 if _, ok := eqA.Data.(*ENull); ok { 1384 if _, ok := eqB.Data.(*EUndefined); ok { 1385 return a.Left, a.Right, true 1386 } 1387 } 1388 1389 // "a === void 0 || a === null" 1390 if _, ok := eqA.Data.(*EUndefined); ok { 1391 if _, ok := eqB.Data.(*ENull); ok { 1392 return b.Left, b.Right, true 1393 } 1394 } 1395 } 1396 } 1397 } 1398 } 1399 1400 return Expr{}, Expr{}, false 1401 } 1402 1403 func CheckEqualityBigInt(a string, b string) (equal bool, ok bool) { 1404 // Equal literals are always equal 1405 if a == b { 1406 return true, true 1407 } 1408 1409 // Unequal literals are unequal if neither has a radix. Leading zeros are 1410 // disallowed in bigint literals without a radix, so in this case we know 1411 // each value is in canonical form. 1412 if (len(a) < 2 || a[0] != '0') && (len(b) < 2 || b[0] != '0') { 1413 return false, true 1414 } 1415 1416 return false, false 1417 } 1418 1419 type EqualityKind uint8 1420 1421 const ( 1422 LooseEquality EqualityKind = iota 1423 StrictEquality 1424 ) 1425 1426 // Returns "equal, ok". If "ok" is false, then nothing is known about the two 1427 // values. If "ok" is true, the equality or inequality of the two values is 1428 // stored in "equal". 1429 func CheckEqualityIfNoSideEffects(left E, right E, kind EqualityKind) (equal bool, ok bool) { 1430 if r, ok := right.(*EInlinedEnum); ok { 1431 return CheckEqualityIfNoSideEffects(left, r.Value.Data, kind) 1432 } 1433 1434 switch l := left.(type) { 1435 case *EInlinedEnum: 1436 return CheckEqualityIfNoSideEffects(l.Value.Data, right, kind) 1437 1438 case *ENull: 1439 switch right.(type) { 1440 case *ENull: 1441 // "null === null" is true 1442 return true, true 1443 1444 case *EUndefined: 1445 // "null == undefined" is true 1446 // "null === undefined" is false 1447 return kind == LooseEquality, true 1448 1449 default: 1450 if IsPrimitiveLiteral(right) { 1451 // "null == (not null or undefined)" is false 1452 return false, true 1453 } 1454 } 1455 1456 case *EUndefined: 1457 switch right.(type) { 1458 case *EUndefined: 1459 // "undefined === undefined" is true 1460 return true, true 1461 1462 case *ENull: 1463 // "undefined == null" is true 1464 // "undefined === null" is false 1465 return kind == LooseEquality, true 1466 1467 default: 1468 if IsPrimitiveLiteral(right) { 1469 // "undefined == (not null or undefined)" is false 1470 return false, true 1471 } 1472 } 1473 1474 case *EBoolean: 1475 switch r := right.(type) { 1476 case *EBoolean: 1477 // "false === false" is true 1478 // "false === true" is false 1479 return l.Value == r.Value, true 1480 1481 case *ENumber: 1482 if kind == LooseEquality { 1483 if l.Value { 1484 // "true == 1" is true 1485 return r.Value == 1, true 1486 } else { 1487 // "false == 0" is true 1488 return r.Value == 0, true 1489 } 1490 } else { 1491 // "true === 1" is false 1492 // "false === 0" is false 1493 return false, true 1494 } 1495 1496 case *ENull, *EUndefined: 1497 // "(not null or undefined) == undefined" is false 1498 return false, true 1499 } 1500 1501 case *ENumber: 1502 switch r := right.(type) { 1503 case *ENumber: 1504 // "0 === 0" is true 1505 // "0 === 1" is false 1506 return l.Value == r.Value, true 1507 1508 case *EBoolean: 1509 if kind == LooseEquality { 1510 if r.Value { 1511 // "1 == true" is true 1512 return l.Value == 1, true 1513 } else { 1514 // "0 == false" is true 1515 return l.Value == 0, true 1516 } 1517 } else { 1518 // "1 === true" is false 1519 // "0 === false" is false 1520 return false, true 1521 } 1522 1523 case *ENull, *EUndefined: 1524 // "(not null or undefined) == undefined" is false 1525 return false, true 1526 } 1527 1528 case *EBigInt: 1529 switch r := right.(type) { 1530 case *EBigInt: 1531 // "0n === 0n" is true 1532 // "0n === 1n" is false 1533 return CheckEqualityBigInt(l.Value, r.Value) 1534 1535 case *ENull, *EUndefined: 1536 // "(not null or undefined) == undefined" is false 1537 return false, true 1538 } 1539 1540 case *EString: 1541 switch r := right.(type) { 1542 case *EString: 1543 // "'a' === 'a'" is true 1544 // "'a' === 'b'" is false 1545 return helpers.UTF16EqualsUTF16(l.Value, r.Value), true 1546 1547 case *ENull, *EUndefined: 1548 // "(not null or undefined) == undefined" is false 1549 return false, true 1550 } 1551 } 1552 1553 return false, false 1554 } 1555 1556 func ValuesLookTheSame(left E, right E) bool { 1557 if b, ok := right.(*EInlinedEnum); ok { 1558 return ValuesLookTheSame(left, b.Value.Data) 1559 } 1560 1561 switch a := left.(type) { 1562 case *EInlinedEnum: 1563 return ValuesLookTheSame(a.Value.Data, right) 1564 1565 case *EIdentifier: 1566 if b, ok := right.(*EIdentifier); ok && a.Ref == b.Ref { 1567 return true 1568 } 1569 1570 case *EDot: 1571 if b, ok := right.(*EDot); ok && a.HasSameFlagsAs(b) && 1572 a.Name == b.Name && ValuesLookTheSame(a.Target.Data, b.Target.Data) { 1573 return true 1574 } 1575 1576 case *EIndex: 1577 if b, ok := right.(*EIndex); ok && a.HasSameFlagsAs(b) && 1578 ValuesLookTheSame(a.Target.Data, b.Target.Data) && ValuesLookTheSame(a.Index.Data, b.Index.Data) { 1579 return true 1580 } 1581 1582 case *EIf: 1583 if b, ok := right.(*EIf); ok && ValuesLookTheSame(a.Test.Data, b.Test.Data) && 1584 ValuesLookTheSame(a.Yes.Data, b.Yes.Data) && ValuesLookTheSame(a.No.Data, b.No.Data) { 1585 return true 1586 } 1587 1588 case *EUnary: 1589 if b, ok := right.(*EUnary); ok && a.Op == b.Op && ValuesLookTheSame(a.Value.Data, b.Value.Data) { 1590 return true 1591 } 1592 1593 case *EBinary: 1594 if b, ok := right.(*EBinary); ok && a.Op == b.Op && ValuesLookTheSame(a.Left.Data, b.Left.Data) && 1595 ValuesLookTheSame(a.Right.Data, b.Right.Data) { 1596 return true 1597 } 1598 1599 case *ECall: 1600 if b, ok := right.(*ECall); ok && a.HasSameFlagsAs(b) && 1601 len(a.Args) == len(b.Args) && ValuesLookTheSame(a.Target.Data, b.Target.Data) { 1602 for i := range a.Args { 1603 if !ValuesLookTheSame(a.Args[i].Data, b.Args[i].Data) { 1604 return false 1605 } 1606 } 1607 return true 1608 } 1609 1610 // Special-case to distinguish between negative an non-negative zero when mangling 1611 // "a ? -0 : 0" => "a ? -0 : 0" 1612 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness 1613 case *ENumber: 1614 b, ok := right.(*ENumber) 1615 if ok && a.Value == 0 && b.Value == 0 && math.Signbit(a.Value) != math.Signbit(b.Value) { 1616 return false 1617 } 1618 } 1619 1620 equal, ok := CheckEqualityIfNoSideEffects(left, right, StrictEquality) 1621 return ok && equal 1622 } 1623 1624 func TryToInsertOptionalChain(test Expr, expr Expr) bool { 1625 switch e := expr.Data.(type) { 1626 case *EDot: 1627 if ValuesLookTheSame(test.Data, e.Target.Data) { 1628 e.OptionalChain = OptionalChainStart 1629 return true 1630 } 1631 if TryToInsertOptionalChain(test, e.Target) { 1632 if e.OptionalChain == OptionalChainNone { 1633 e.OptionalChain = OptionalChainContinue 1634 } 1635 return true 1636 } 1637 1638 case *EIndex: 1639 if ValuesLookTheSame(test.Data, e.Target.Data) { 1640 e.OptionalChain = OptionalChainStart 1641 return true 1642 } 1643 if TryToInsertOptionalChain(test, e.Target) { 1644 if e.OptionalChain == OptionalChainNone { 1645 e.OptionalChain = OptionalChainContinue 1646 } 1647 return true 1648 } 1649 1650 case *ECall: 1651 if ValuesLookTheSame(test.Data, e.Target.Data) { 1652 e.OptionalChain = OptionalChainStart 1653 return true 1654 } 1655 if TryToInsertOptionalChain(test, e.Target) { 1656 if e.OptionalChain == OptionalChainNone { 1657 e.OptionalChain = OptionalChainContinue 1658 } 1659 return true 1660 } 1661 } 1662 1663 return false 1664 } 1665 1666 func joinStrings(a []uint16, b []uint16) []uint16 { 1667 data := make([]uint16, len(a)+len(b)) 1668 copy(data[:len(a)], a) 1669 copy(data[len(a):], b) 1670 return data 1671 } 1672 1673 // String concatenation with numbers is required by the TypeScript compiler for 1674 // "constant expression" handling in enums. However, we don't want to introduce 1675 // correctness bugs by accidentally stringifying a number differently than how 1676 // a real JavaScript VM would do it. So we are conservative and we only do this 1677 // when we know it'll be the same result. 1678 func TryToStringOnNumberSafely(n float64, radix int) (string, bool) { 1679 if i := int32(n); float64(i) == n { 1680 return strconv.FormatInt(int64(i), radix), true 1681 } 1682 if math.IsNaN(n) { 1683 return "NaN", true 1684 } 1685 if math.IsInf(n, 1) { 1686 return "Infinity", true 1687 } 1688 if math.IsInf(n, -1) { 1689 return "-Infinity", true 1690 } 1691 return "", false 1692 } 1693 1694 // Note: We don't know if this is string addition yet at this point 1695 func foldAdditionPreProcess(expr Expr) Expr { 1696 switch e := expr.Data.(type) { 1697 case *EInlinedEnum: 1698 // "See through" inline enum constants 1699 expr = e.Value 1700 1701 case *EArray: 1702 // "[] + x" => "'' + x" 1703 // "[1,2] + x" => "'1,2' + x" 1704 items := make([]string, 0, len(e.Items)) 1705 for _, item := range e.Items { 1706 switch item.Data.(type) { 1707 case *EUndefined, *ENull: 1708 items = append(items, "") 1709 continue 1710 } 1711 if str, ok := ToStringWithoutSideEffects(item.Data); ok { 1712 item.Data = &EString{Value: helpers.StringToUTF16(str)} 1713 } 1714 str, ok := item.Data.(*EString) 1715 if !ok { 1716 break 1717 } 1718 items = append(items, helpers.UTF16ToString(str.Value)) 1719 } 1720 if len(items) == len(e.Items) { 1721 expr.Data = &EString{Value: helpers.StringToUTF16(strings.Join(items, ","))} 1722 } 1723 1724 case *EObject: 1725 // "{} + x" => "'[object Object]' + x" 1726 if len(e.Properties) == 0 { 1727 expr.Data = &EString{Value: helpers.StringToUTF16("[object Object]")} 1728 } 1729 } 1730 return expr 1731 } 1732 1733 type StringAdditionKind uint8 1734 1735 const ( 1736 StringAdditionNormal StringAdditionKind = iota 1737 StringAdditionWithNestedLeft 1738 ) 1739 1740 // This function intentionally avoids mutating the input AST so it can be 1741 // called after the AST has been frozen (i.e. after parsing ends). 1742 func FoldStringAddition(left Expr, right Expr, kind StringAdditionKind) Expr { 1743 left = foldAdditionPreProcess(left) 1744 right = foldAdditionPreProcess(right) 1745 1746 // Transforming the left operand into a string is not safe if it comes from 1747 // a nested AST node. The following transforms are invalid: 1748 // 1749 // "0 + 1 + 'x'" => "0 + '1x'" 1750 // "0 + 1 + `${x}`" => "0 + `1${x}`" 1751 // 1752 if kind != StringAdditionWithNestedLeft { 1753 switch right.Data.(type) { 1754 case *EString, *ETemplate: 1755 if str, ok := ToStringWithoutSideEffects(left.Data); ok { 1756 left.Data = &EString{Value: helpers.StringToUTF16(str)} 1757 } 1758 } 1759 } 1760 1761 switch l := left.Data.(type) { 1762 case *EString: 1763 // "'x' + 0" => "'x' + '0'" 1764 if str, ok := ToStringWithoutSideEffects(right.Data); ok { 1765 right.Data = &EString{Value: helpers.StringToUTF16(str)} 1766 } 1767 1768 switch r := right.Data.(type) { 1769 case *EString: 1770 // "'x' + 'y'" => "'xy'" 1771 return Expr{Loc: left.Loc, Data: &EString{ 1772 Value: joinStrings(l.Value, r.Value), 1773 PreferTemplate: l.PreferTemplate || r.PreferTemplate, 1774 }} 1775 1776 case *ETemplate: 1777 if r.TagOrNil.Data == nil { 1778 // "'x' + `y${z}`" => "`xy${z}`" 1779 return Expr{Loc: left.Loc, Data: &ETemplate{ 1780 HeadLoc: left.Loc, 1781 HeadCooked: joinStrings(l.Value, r.HeadCooked), 1782 Parts: r.Parts, 1783 }} 1784 } 1785 } 1786 1787 // "'' + typeof x" => "typeof x" 1788 if len(l.Value) == 0 && KnownPrimitiveType(right.Data) == PrimitiveString { 1789 return right 1790 } 1791 1792 case *ETemplate: 1793 if l.TagOrNil.Data == nil { 1794 // "`${x}` + 0" => "`${x}` + '0'" 1795 if str, ok := ToStringWithoutSideEffects(right.Data); ok { 1796 right.Data = &EString{Value: helpers.StringToUTF16(str)} 1797 } 1798 1799 switch r := right.Data.(type) { 1800 case *EString: 1801 // "`${x}y` + 'z'" => "`${x}yz`" 1802 n := len(l.Parts) 1803 head := l.HeadCooked 1804 parts := make([]TemplatePart, n) 1805 if n == 0 { 1806 head = joinStrings(head, r.Value) 1807 } else { 1808 copy(parts, l.Parts) 1809 parts[n-1].TailCooked = joinStrings(parts[n-1].TailCooked, r.Value) 1810 } 1811 return Expr{Loc: left.Loc, Data: &ETemplate{ 1812 HeadLoc: l.HeadLoc, 1813 HeadCooked: head, 1814 Parts: parts, 1815 }} 1816 1817 case *ETemplate: 1818 if r.TagOrNil.Data == nil { 1819 // "`${a}b` + `x${y}`" => "`${a}bx${y}`" 1820 n := len(l.Parts) 1821 head := l.HeadCooked 1822 parts := make([]TemplatePart, n+len(r.Parts)) 1823 copy(parts[n:], r.Parts) 1824 if n == 0 { 1825 head = joinStrings(head, r.HeadCooked) 1826 } else { 1827 copy(parts[:n], l.Parts) 1828 parts[n-1].TailCooked = joinStrings(parts[n-1].TailCooked, r.HeadCooked) 1829 } 1830 return Expr{Loc: left.Loc, Data: &ETemplate{ 1831 HeadLoc: l.HeadLoc, 1832 HeadCooked: head, 1833 Parts: parts, 1834 }} 1835 } 1836 } 1837 } 1838 } 1839 1840 // "typeof x + ''" => "typeof x" 1841 if r, ok := right.Data.(*EString); ok && len(r.Value) == 0 && KnownPrimitiveType(left.Data) == PrimitiveString { 1842 return left 1843 } 1844 1845 return Expr{} 1846 } 1847 1848 // "`a${'b'}c`" => "`abc`" 1849 // 1850 // This function intentionally avoids mutating the input AST so it can be 1851 // called after the AST has been frozen (i.e. after parsing ends). 1852 func InlinePrimitivesIntoTemplate(loc logger.Loc, e *ETemplate) Expr { 1853 // Can't inline strings if there's a custom template tag 1854 if e.TagOrNil.Data != nil { 1855 return Expr{Loc: loc, Data: e} 1856 } 1857 1858 headCooked := e.HeadCooked 1859 parts := make([]TemplatePart, 0, len(e.Parts)) 1860 1861 for _, part := range e.Parts { 1862 if value, ok := part.Value.Data.(*EInlinedEnum); ok { 1863 part.Value = value.Value 1864 } 1865 if str, ok := ToStringWithoutSideEffects(part.Value.Data); ok { 1866 part.Value.Data = &EString{Value: helpers.StringToUTF16(str)} 1867 } 1868 if str, ok := part.Value.Data.(*EString); ok { 1869 if len(parts) == 0 { 1870 headCooked = append(append(headCooked, str.Value...), part.TailCooked...) 1871 } else { 1872 prevPart := &parts[len(parts)-1] 1873 prevPart.TailCooked = append(append(prevPart.TailCooked, str.Value...), part.TailCooked...) 1874 } 1875 } else { 1876 parts = append(parts, part) 1877 } 1878 } 1879 1880 // Become a plain string if there are no substitutions 1881 if len(parts) == 0 { 1882 return Expr{Loc: loc, Data: &EString{ 1883 Value: headCooked, 1884 PreferTemplate: true, 1885 }} 1886 } 1887 1888 return Expr{Loc: loc, Data: &ETemplate{ 1889 HeadLoc: e.HeadLoc, 1890 HeadCooked: headCooked, 1891 Parts: parts, 1892 }} 1893 } 1894 1895 type SideEffects uint8 1896 1897 const ( 1898 CouldHaveSideEffects SideEffects = iota 1899 NoSideEffects 1900 ) 1901 1902 func ToNullOrUndefinedWithSideEffects(data E) (isNullOrUndefined bool, sideEffects SideEffects, ok bool) { 1903 switch e := data.(type) { 1904 case *EAnnotation: 1905 isNullOrUndefined, sideEffects, ok = ToNullOrUndefinedWithSideEffects(e.Value.Data) 1906 if e.Flags.Has(CanBeRemovedIfUnusedFlag) { 1907 sideEffects = NoSideEffects 1908 } 1909 return 1910 1911 case *EInlinedEnum: 1912 return ToNullOrUndefinedWithSideEffects(e.Value.Data) 1913 1914 // Never null or undefined 1915 case *EBoolean, *ENumber, *EString, *ERegExp, 1916 *EFunction, *EArrow, *EBigInt: 1917 return false, NoSideEffects, true 1918 1919 // Never null or undefined 1920 case *EObject, *EArray, *EClass: 1921 return false, CouldHaveSideEffects, true 1922 1923 // Always null or undefined 1924 case *ENull, *EUndefined: 1925 return true, NoSideEffects, true 1926 1927 case *EUnary: 1928 switch e.Op { 1929 case 1930 // Always number or bigint 1931 UnOpPos, UnOpNeg, UnOpCpl, 1932 UnOpPreDec, UnOpPreInc, UnOpPostDec, UnOpPostInc, 1933 // Always boolean 1934 UnOpNot, UnOpDelete: 1935 return false, CouldHaveSideEffects, true 1936 1937 // Always boolean 1938 case UnOpTypeof: 1939 if e.WasOriginallyTypeofIdentifier { 1940 // Expressions such as "typeof x" never have any side effects 1941 return false, NoSideEffects, true 1942 } 1943 return false, CouldHaveSideEffects, true 1944 1945 // Always undefined 1946 case UnOpVoid: 1947 return true, CouldHaveSideEffects, true 1948 } 1949 1950 case *EBinary: 1951 switch e.Op { 1952 case 1953 // Always string or number or bigint 1954 BinOpAdd, BinOpAddAssign, 1955 // Always number or bigint 1956 BinOpSub, BinOpMul, BinOpDiv, BinOpRem, BinOpPow, 1957 BinOpSubAssign, BinOpMulAssign, BinOpDivAssign, BinOpRemAssign, BinOpPowAssign, 1958 BinOpShl, BinOpShr, BinOpUShr, 1959 BinOpShlAssign, BinOpShrAssign, BinOpUShrAssign, 1960 BinOpBitwiseOr, BinOpBitwiseAnd, BinOpBitwiseXor, 1961 BinOpBitwiseOrAssign, BinOpBitwiseAndAssign, BinOpBitwiseXorAssign, 1962 // Always boolean 1963 BinOpLt, BinOpLe, BinOpGt, BinOpGe, BinOpIn, BinOpInstanceof, 1964 BinOpLooseEq, BinOpLooseNe, BinOpStrictEq, BinOpStrictNe: 1965 return false, CouldHaveSideEffects, true 1966 1967 case BinOpComma: 1968 if isNullOrUndefined, _, ok := ToNullOrUndefinedWithSideEffects(e.Right.Data); ok { 1969 return isNullOrUndefined, CouldHaveSideEffects, true 1970 } 1971 } 1972 } 1973 1974 return false, NoSideEffects, false 1975 } 1976 1977 func ToBooleanWithSideEffects(data E) (boolean bool, sideEffects SideEffects, ok bool) { 1978 switch e := data.(type) { 1979 case *EAnnotation: 1980 boolean, sideEffects, ok = ToBooleanWithSideEffects(e.Value.Data) 1981 if e.Flags.Has(CanBeRemovedIfUnusedFlag) { 1982 sideEffects = NoSideEffects 1983 } 1984 return 1985 1986 case *EInlinedEnum: 1987 return ToBooleanWithSideEffects(e.Value.Data) 1988 1989 case *ENull, *EUndefined: 1990 return false, NoSideEffects, true 1991 1992 case *EBoolean: 1993 return e.Value, NoSideEffects, true 1994 1995 case *ENumber: 1996 return e.Value != 0 && !math.IsNaN(e.Value), NoSideEffects, true 1997 1998 case *EBigInt: 1999 equal, ok := CheckEqualityBigInt(e.Value, "0") 2000 return !equal, NoSideEffects, ok 2001 2002 case *EString: 2003 return len(e.Value) > 0, NoSideEffects, true 2004 2005 case *EFunction, *EArrow, *ERegExp: 2006 return true, NoSideEffects, true 2007 2008 case *EObject, *EArray, *EClass: 2009 return true, CouldHaveSideEffects, true 2010 2011 case *EUnary: 2012 switch e.Op { 2013 case UnOpVoid: 2014 return false, CouldHaveSideEffects, true 2015 2016 case UnOpTypeof: 2017 // Never an empty string 2018 if e.WasOriginallyTypeofIdentifier { 2019 // Expressions such as "typeof x" never have any side effects 2020 return true, NoSideEffects, true 2021 } 2022 return true, CouldHaveSideEffects, true 2023 2024 case UnOpNot: 2025 if boolean, SideEffects, ok := ToBooleanWithSideEffects(e.Value.Data); ok { 2026 return !boolean, SideEffects, true 2027 } 2028 } 2029 2030 case *EBinary: 2031 switch e.Op { 2032 case BinOpLogicalOr: 2033 // "anything || truthy" is truthy 2034 if boolean, _, ok := ToBooleanWithSideEffects(e.Right.Data); ok && boolean { 2035 return true, CouldHaveSideEffects, true 2036 } 2037 2038 case BinOpLogicalAnd: 2039 // "anything && falsy" is falsy 2040 if boolean, _, ok := ToBooleanWithSideEffects(e.Right.Data); ok && !boolean { 2041 return false, CouldHaveSideEffects, true 2042 } 2043 2044 case BinOpComma: 2045 // "anything, truthy/falsy" is truthy/falsy 2046 if boolean, _, ok := ToBooleanWithSideEffects(e.Right.Data); ok { 2047 return boolean, CouldHaveSideEffects, true 2048 } 2049 } 2050 } 2051 2052 return false, CouldHaveSideEffects, false 2053 } 2054 2055 // Simplify syntax when we know it's used inside a boolean context 2056 // 2057 // This function intentionally avoids mutating the input AST so it can be 2058 // called after the AST has been frozen (i.e. after parsing ends). 2059 func (ctx HelperContext) SimplifyBooleanExpr(expr Expr) Expr { 2060 switch e := expr.Data.(type) { 2061 case *EUnary: 2062 if e.Op == UnOpNot { 2063 // "!!a" => "a" 2064 if e2, ok2 := e.Value.Data.(*EUnary); ok2 && e2.Op == UnOpNot { 2065 return ctx.SimplifyBooleanExpr(e2.Value) 2066 } 2067 2068 // "!!!a" => "!a" 2069 return Expr{Loc: expr.Loc, Data: &EUnary{Op: UnOpNot, Value: ctx.SimplifyBooleanExpr(e.Value)}} 2070 } 2071 2072 case *EBinary: 2073 left := e.Left 2074 right := e.Right 2075 2076 switch e.Op { 2077 case BinOpStrictEq, BinOpStrictNe, BinOpLooseEq, BinOpLooseNe: 2078 if r, ok := extractNumericValue(right.Data); ok && r == 0 && isInt32OrUint32(left.Data) { 2079 // If the left is guaranteed to be an integer (e.g. not NaN, 2080 // Infinity, or a non-numeric value) then a test against zero 2081 // in a boolean context is unnecessary because the value is 2082 // only truthy if it's not zero. 2083 if e.Op == BinOpStrictNe || e.Op == BinOpLooseNe { 2084 // "if ((a | b) !== 0)" => "if (a | b)" 2085 return left 2086 } else { 2087 // "if ((a | b) === 0)" => "if (!(a | b))" 2088 return Not(left) 2089 } 2090 } 2091 2092 case BinOpLogicalAnd: 2093 // "if (!!a && !!b)" => "if (a && b)" 2094 left = ctx.SimplifyBooleanExpr(left) 2095 right = ctx.SimplifyBooleanExpr(right) 2096 2097 if boolean, SideEffects, ok := ToBooleanWithSideEffects(right.Data); ok && boolean && SideEffects == NoSideEffects { 2098 // "if (anything && truthyNoSideEffects)" => "if (anything)" 2099 return left 2100 } 2101 2102 case BinOpLogicalOr: 2103 // "if (!!a || !!b)" => "if (a || b)" 2104 left = ctx.SimplifyBooleanExpr(left) 2105 right = ctx.SimplifyBooleanExpr(right) 2106 2107 if boolean, SideEffects, ok := ToBooleanWithSideEffects(right.Data); ok && !boolean && SideEffects == NoSideEffects { 2108 // "if (anything || falsyNoSideEffects)" => "if (anything)" 2109 return left 2110 } 2111 } 2112 2113 if left != e.Left || right != e.Right { 2114 return Expr{Loc: expr.Loc, Data: &EBinary{Op: e.Op, Left: left, Right: right}} 2115 } 2116 2117 case *EIf: 2118 // "if (a ? !!b : !!c)" => "if (a ? b : c)" 2119 yes := ctx.SimplifyBooleanExpr(e.Yes) 2120 no := ctx.SimplifyBooleanExpr(e.No) 2121 2122 if boolean, SideEffects, ok := ToBooleanWithSideEffects(yes.Data); ok && SideEffects == NoSideEffects { 2123 if boolean { 2124 // "if (anything1 ? truthyNoSideEffects : anything2)" => "if (anything1 || anything2)" 2125 return JoinWithLeftAssociativeOp(BinOpLogicalOr, e.Test, no) 2126 } else { 2127 // "if (anything1 ? falsyNoSideEffects : anything2)" => "if (!anything1 || anything2)" 2128 return JoinWithLeftAssociativeOp(BinOpLogicalAnd, Not(e.Test), no) 2129 } 2130 } 2131 2132 if boolean, SideEffects, ok := ToBooleanWithSideEffects(no.Data); ok && SideEffects == NoSideEffects { 2133 if boolean { 2134 // "if (anything1 ? anything2 : truthyNoSideEffects)" => "if (!anything1 || anything2)" 2135 return JoinWithLeftAssociativeOp(BinOpLogicalOr, Not(e.Test), yes) 2136 } else { 2137 // "if (anything1 ? anything2 : falsyNoSideEffects)" => "if (anything1 && anything2)" 2138 return JoinWithLeftAssociativeOp(BinOpLogicalAnd, e.Test, yes) 2139 } 2140 } 2141 2142 if yes != e.Yes || no != e.No { 2143 return Expr{Loc: expr.Loc, Data: &EIf{Test: e.Test, Yes: yes, No: no}} 2144 } 2145 2146 default: 2147 // "!![]" => "true" 2148 if boolean, sideEffects, ok := ToBooleanWithSideEffects(expr.Data); ok && (sideEffects == NoSideEffects || ctx.ExprCanBeRemovedIfUnused(expr)) { 2149 return Expr{Loc: expr.Loc, Data: &EBoolean{Value: boolean}} 2150 } 2151 } 2152 2153 return expr 2154 } 2155 2156 type StmtsCanBeRemovedIfUnusedFlags uint8 2157 2158 const ( 2159 KeepExportClauses StmtsCanBeRemovedIfUnusedFlags = 1 << iota 2160 ReturnCanBeRemovedIfUnused 2161 ) 2162 2163 func (ctx HelperContext) StmtsCanBeRemovedIfUnused(stmts []Stmt, flags StmtsCanBeRemovedIfUnusedFlags) bool { 2164 for _, stmt := range stmts { 2165 switch s := stmt.Data.(type) { 2166 case *SFunction, *SEmpty: 2167 // These never have side effects 2168 2169 case *SImport: 2170 // Let these be removed if they are unused. Note that we also need to 2171 // check if the imported file is marked as "sideEffects: false" before we 2172 // can remove a SImport statement. Otherwise the import must be kept for 2173 // its side effects. 2174 2175 case *SClass: 2176 if !ctx.ClassCanBeRemovedIfUnused(s.Class) { 2177 return false 2178 } 2179 2180 case *SReturn: 2181 if (flags&ReturnCanBeRemovedIfUnused) == 0 || (s.ValueOrNil.Data != nil && !ctx.ExprCanBeRemovedIfUnused(s.ValueOrNil)) { 2182 return false 2183 } 2184 2185 case *SExpr: 2186 if !ctx.ExprCanBeRemovedIfUnused(s.Value) { 2187 if s.IsFromClassOrFnThatCanBeRemovedIfUnused { 2188 // This statement was automatically generated when lowering a class 2189 // or function that we were able to analyze as having no side effects 2190 // before lowering. So we consider it to be removable. The assumption 2191 // here is that we are seeing at least all of the statements from the 2192 // class lowering operation all at once (although we may possibly be 2193 // seeing even more statements than that). Since we're making a binary 2194 // all-or-nothing decision about the side effects of these statements, 2195 // we can safely consider these to be side-effect free because we 2196 // aren't in danger of partially dropping some of the class setup code. 2197 } else { 2198 return false 2199 } 2200 } 2201 2202 case *SLocal: 2203 // "await" is a side effect because it affects code timing 2204 if s.Kind == LocalAwaitUsing { 2205 return false 2206 } 2207 2208 for _, decl := range s.Decls { 2209 // Check that the bindings are side-effect free 2210 switch binding := decl.Binding.Data.(type) { 2211 case *BIdentifier: 2212 // An identifier binding has no side effects 2213 2214 case *BArray: 2215 // Destructuring the initializer has no side effects if the 2216 // initializer is an array, since we assume the iterator is then 2217 // the built-in side-effect free array iterator. 2218 if _, ok := decl.ValueOrNil.Data.(*EArray); ok { 2219 for _, item := range binding.Items { 2220 if item.DefaultValueOrNil.Data != nil && !ctx.ExprCanBeRemovedIfUnused(item.DefaultValueOrNil) { 2221 return false 2222 } 2223 2224 switch item.Binding.Data.(type) { 2225 case *BIdentifier, *BMissing: 2226 // Right now we only handle an array pattern with identifier 2227 // bindings or with empty holes (i.e. "missing" elements) 2228 default: 2229 return false 2230 } 2231 } 2232 break 2233 } 2234 return false 2235 2236 default: 2237 // Consider anything else to potentially have side effects 2238 return false 2239 } 2240 2241 // Check that the initializer is side-effect free 2242 if decl.ValueOrNil.Data != nil { 2243 if !ctx.ExprCanBeRemovedIfUnused(decl.ValueOrNil) { 2244 return false 2245 } 2246 2247 // "using" declarations are only side-effect free if they are initialized to null or undefined 2248 if s.Kind.IsUsing() { 2249 if t := KnownPrimitiveType(decl.ValueOrNil.Data); t != PrimitiveNull && t != PrimitiveUndefined { 2250 return false 2251 } 2252 } 2253 } 2254 } 2255 2256 case *STry: 2257 if !ctx.StmtsCanBeRemovedIfUnused(s.Block.Stmts, 0) || (s.Finally != nil && !ctx.StmtsCanBeRemovedIfUnused(s.Finally.Block.Stmts, 0)) { 2258 return false 2259 } 2260 2261 case *SExportFrom: 2262 // Exports are tracked separately, so this isn't necessary 2263 2264 case *SExportClause: 2265 if (flags & KeepExportClauses) != 0 { 2266 return false 2267 } 2268 2269 case *SExportDefault: 2270 switch s2 := s.Value.Data.(type) { 2271 case *SExpr: 2272 if !ctx.ExprCanBeRemovedIfUnused(s2.Value) { 2273 return false 2274 } 2275 2276 case *SFunction: 2277 // These never have side effects 2278 2279 case *SClass: 2280 if !ctx.ClassCanBeRemovedIfUnused(s2.Class) { 2281 return false 2282 } 2283 2284 default: 2285 panic("Internal error") 2286 } 2287 2288 default: 2289 // Assume that all statements not explicitly special-cased here have side 2290 // effects, and cannot be removed even if unused 2291 return false 2292 } 2293 } 2294 2295 return true 2296 } 2297 2298 func (ctx HelperContext) ClassCanBeRemovedIfUnused(class Class) bool { 2299 if len(class.Decorators) > 0 { 2300 return false 2301 } 2302 2303 // Note: This check is incorrect. Extending a non-constructible object can 2304 // throw an error, which is a side effect: 2305 // 2306 // async function x() {} 2307 // class y extends x {} 2308 // 2309 // But refusing to tree-shake every class with a base class is not a useful 2310 // thing for a bundler to do. So we pretend that this edge case doesn't 2311 // exist. At the time of writing, both Rollup and Terser don't consider this 2312 // to be a side effect either. 2313 if class.ExtendsOrNil.Data != nil && !ctx.ExprCanBeRemovedIfUnused(class.ExtendsOrNil) { 2314 return false 2315 } 2316 2317 for _, property := range class.Properties { 2318 if property.Kind == PropertyClassStaticBlock { 2319 if !ctx.StmtsCanBeRemovedIfUnused(property.ClassStaticBlock.Block.Stmts, 0) { 2320 return false 2321 } 2322 continue 2323 } 2324 2325 if len(property.Decorators) > 0 { 2326 return false 2327 } 2328 2329 if property.Flags.Has(PropertyIsComputed) && !IsPrimitiveLiteral(property.Key.Data) && !IsSymbolInstance(property.Key.Data) { 2330 return false 2331 } 2332 2333 if property.Kind.IsMethodDefinition() { 2334 if fn, ok := property.ValueOrNil.Data.(*EFunction); ok { 2335 for _, arg := range fn.Fn.Args { 2336 if len(arg.Decorators) > 0 { 2337 return false 2338 } 2339 } 2340 } 2341 } 2342 2343 if property.Flags.Has(PropertyIsStatic) { 2344 if property.ValueOrNil.Data != nil && !ctx.ExprCanBeRemovedIfUnused(property.ValueOrNil) { 2345 return false 2346 } 2347 2348 if property.InitializerOrNil.Data != nil && !ctx.ExprCanBeRemovedIfUnused(property.InitializerOrNil) { 2349 return false 2350 } 2351 2352 // Legacy TypeScript static class fields are considered to have side 2353 // effects because they use assign semantics, not define semantics, and 2354 // that can trigger getters. For example: 2355 // 2356 // class Foo { 2357 // static set foo(x) { importantSideEffect(x) } 2358 // } 2359 // class Bar extends Foo { 2360 // foo = 1 2361 // } 2362 // 2363 // This happens in TypeScript when "useDefineForClassFields" is disabled 2364 // because TypeScript (and esbuild) transforms the above class into this: 2365 // 2366 // class Foo { 2367 // static set foo(x) { importantSideEffect(x); } 2368 // } 2369 // class Bar extends Foo { 2370 // } 2371 // Bar.foo = 1; 2372 // 2373 // Note that it's not possible to analyze the base class to determine that 2374 // these assignments are side-effect free. For example: 2375 // 2376 // // Some code that already ran before your code 2377 // Object.defineProperty(Object.prototype, 'foo', { 2378 // set(x) { imporantSideEffect(x) } 2379 // }) 2380 // 2381 // // Your code 2382 // class Foo { 2383 // static foo = 1 2384 // } 2385 // 2386 if property.Kind == PropertyField && !class.UseDefineForClassFields { 2387 return false 2388 } 2389 } 2390 } 2391 2392 return true 2393 } 2394 2395 func (ctx HelperContext) ExprCanBeRemovedIfUnused(expr Expr) bool { 2396 switch e := expr.Data.(type) { 2397 case *EAnnotation: 2398 return e.Flags.Has(CanBeRemovedIfUnusedFlag) 2399 2400 case *EInlinedEnum: 2401 return ctx.ExprCanBeRemovedIfUnused(e.Value) 2402 2403 case *ENull, *EUndefined, *EMissing, *EBoolean, *ENumber, *EBigInt, 2404 *EString, *EThis, *ERegExp, *EFunction, *EArrow, *EImportMeta: 2405 return true 2406 2407 case *EDot: 2408 return e.CanBeRemovedIfUnused 2409 2410 case *EClass: 2411 return ctx.ClassCanBeRemovedIfUnused(e.Class) 2412 2413 case *EIdentifier: 2414 if e.MustKeepDueToWithStmt { 2415 return false 2416 } 2417 2418 // Unbound identifiers cannot be removed because they can have side effects. 2419 // One possible side effect is throwing a ReferenceError if they don't exist. 2420 // Another one is a getter with side effects on the global object: 2421 // 2422 // Object.defineProperty(globalThis, 'x', { 2423 // get() { 2424 // sideEffect(); 2425 // }, 2426 // }); 2427 // 2428 // Be very careful about this possibility. It's tempting to treat all 2429 // identifier expressions as not having side effects but that's wrong. We 2430 // must make sure they have been declared by the code we are currently 2431 // compiling before we can tell that they have no side effects. 2432 // 2433 // Note that we currently ignore ReferenceErrors due to TDZ access. This is 2434 // incorrect but proper TDZ analysis is very complicated and would have to 2435 // be very conservative, which would inhibit a lot of optimizations of code 2436 // inside closures. This may need to be revisited if it proves problematic. 2437 if e.CanBeRemovedIfUnused || !ctx.isUnbound(e.Ref) { 2438 return true 2439 } 2440 2441 case *EImportIdentifier: 2442 // References to an ES6 import item are always side-effect free in an 2443 // ECMAScript environment. 2444 // 2445 // They could technically have side effects if the imported module is a 2446 // CommonJS module and the import item was translated to a property access 2447 // (which esbuild's bundler does) and the property has a getter with side 2448 // effects. 2449 // 2450 // But this is very unlikely and respecting this edge case would mean 2451 // disabling tree shaking of all code that references an export from a 2452 // CommonJS module. It would also likely violate the expectations of some 2453 // developers because the code *looks* like it should be able to be tree 2454 // shaken. 2455 // 2456 // So we deliberately ignore this edge case and always treat import item 2457 // references as being side-effect free. 2458 return true 2459 2460 case *EIf: 2461 return ctx.ExprCanBeRemovedIfUnused(e.Test) && 2462 ((ctx.isSideEffectFreeUnboundIdentifierRef(e.Yes, e.Test, true) || ctx.ExprCanBeRemovedIfUnused(e.Yes)) && 2463 (ctx.isSideEffectFreeUnboundIdentifierRef(e.No, e.Test, false) || ctx.ExprCanBeRemovedIfUnused(e.No))) 2464 2465 case *EArray: 2466 for _, item := range e.Items { 2467 if spread, ok := item.Data.(*ESpread); ok { 2468 if _, ok := spread.Value.Data.(*EArray); ok { 2469 // Spread of an inline array such as "[...[x]]" is side-effect free 2470 item = spread.Value 2471 } 2472 } 2473 2474 if !ctx.ExprCanBeRemovedIfUnused(item) { 2475 return false 2476 } 2477 } 2478 return true 2479 2480 case *EObject: 2481 for _, property := range e.Properties { 2482 // The key must still be evaluated if it's computed or a spread 2483 if property.Kind == PropertySpread { 2484 return false 2485 } 2486 if property.Flags.Has(PropertyIsComputed) && !IsPrimitiveLiteral(property.Key.Data) && !IsSymbolInstance(property.Key.Data) { 2487 return false 2488 } 2489 if property.ValueOrNil.Data != nil && !ctx.ExprCanBeRemovedIfUnused(property.ValueOrNil) { 2490 return false 2491 } 2492 } 2493 return true 2494 2495 case *ECall: 2496 canCallBeRemoved := e.CanBeUnwrappedIfUnused 2497 2498 // A call that has been marked "__PURE__" can be removed if all arguments 2499 // can be removed. The annotation causes us to ignore the target. 2500 if canCallBeRemoved { 2501 for _, arg := range e.Args { 2502 if !ctx.ExprCanBeRemovedIfUnused(arg) { 2503 return false 2504 } 2505 } 2506 return true 2507 } 2508 2509 case *ENew: 2510 // A constructor call that has been marked "__PURE__" can be removed if all 2511 // arguments can be removed. The annotation causes us to ignore the target. 2512 if e.CanBeUnwrappedIfUnused { 2513 for _, arg := range e.Args { 2514 if !ctx.ExprCanBeRemovedIfUnused(arg) { 2515 return false 2516 } 2517 } 2518 return true 2519 } 2520 2521 case *EUnary: 2522 switch e.Op { 2523 // These operators must not have any type conversions that can execute code 2524 // such as "toString" or "valueOf". They must also never throw any exceptions. 2525 case UnOpVoid, UnOpNot: 2526 return ctx.ExprCanBeRemovedIfUnused(e.Value) 2527 2528 // The "typeof" operator doesn't do any type conversions so it can be removed 2529 // if the result is unused and the operand has no side effects. However, it 2530 // has a special case where if the operand is an identifier expression such 2531 // as "typeof x" and "x" doesn't exist, no reference error is thrown so the 2532 // operation has no side effects. 2533 case UnOpTypeof: 2534 if _, ok := e.Value.Data.(*EIdentifier); ok && e.WasOriginallyTypeofIdentifier { 2535 // Expressions such as "typeof x" never have any side effects 2536 return true 2537 } 2538 return ctx.ExprCanBeRemovedIfUnused(e.Value) 2539 } 2540 2541 case *EBinary: 2542 switch e.Op { 2543 // These operators must not have any type conversions that can execute code 2544 // such as "toString" or "valueOf". They must also never throw any exceptions. 2545 case BinOpStrictEq, BinOpStrictNe, BinOpComma, BinOpNullishCoalescing: 2546 return ctx.ExprCanBeRemovedIfUnused(e.Left) && ctx.ExprCanBeRemovedIfUnused(e.Right) 2547 2548 // Special-case "||" to make sure "typeof x === 'undefined' || x" can be removed 2549 case BinOpLogicalOr: 2550 return ctx.ExprCanBeRemovedIfUnused(e.Left) && 2551 (ctx.isSideEffectFreeUnboundIdentifierRef(e.Right, e.Left, false) || ctx.ExprCanBeRemovedIfUnused(e.Right)) 2552 2553 // Special-case "&&" to make sure "typeof x !== 'undefined' && x" can be removed 2554 case BinOpLogicalAnd: 2555 return ctx.ExprCanBeRemovedIfUnused(e.Left) && 2556 (ctx.isSideEffectFreeUnboundIdentifierRef(e.Right, e.Left, true) || ctx.ExprCanBeRemovedIfUnused(e.Right)) 2557 2558 // For "==" and "!=", pretend the operator was actually "===" or "!==". If 2559 // we know that we can convert it to "==" or "!=", then we can consider the 2560 // operator itself to have no side effects. This matters because our mangle 2561 // logic will convert "typeof x === 'object'" into "typeof x == 'object'" 2562 // and since "typeof x === 'object'" is considered to be side-effect free, 2563 // we must also consider "typeof x == 'object'" to be side-effect free. 2564 case BinOpLooseEq, BinOpLooseNe: 2565 return CanChangeStrictToLoose(e.Left, e.Right) && ctx.ExprCanBeRemovedIfUnused(e.Left) && ctx.ExprCanBeRemovedIfUnused(e.Right) 2566 2567 // Special-case "<" and ">" with string, number, or bigint arguments 2568 case BinOpLt, BinOpGt, BinOpLe, BinOpGe: 2569 left := KnownPrimitiveType(e.Left.Data) 2570 switch left { 2571 case PrimitiveString, PrimitiveNumber, PrimitiveBigInt: 2572 return KnownPrimitiveType(e.Right.Data) == left && ctx.ExprCanBeRemovedIfUnused(e.Left) && ctx.ExprCanBeRemovedIfUnused(e.Right) 2573 } 2574 } 2575 2576 case *ETemplate: 2577 // A template can be removed if it has no tag and every value has no side 2578 // effects and results in some kind of primitive, since all primitives 2579 // have a "ToString" operation with no side effects. 2580 if e.TagOrNil.Data == nil || e.CanBeUnwrappedIfUnused { 2581 for _, part := range e.Parts { 2582 if !ctx.ExprCanBeRemovedIfUnused(part.Value) || KnownPrimitiveType(part.Value.Data) == PrimitiveUnknown { 2583 return false 2584 } 2585 } 2586 return true 2587 } 2588 } 2589 2590 // Assume all other expression types have side effects and cannot be removed 2591 return false 2592 } 2593 2594 func (ctx HelperContext) isSideEffectFreeUnboundIdentifierRef(value Expr, guardCondition Expr, isYesBranch bool) bool { 2595 if id, ok := value.Data.(*EIdentifier); ok && ctx.isUnbound(id.Ref) { 2596 if binary, ok := guardCondition.Data.(*EBinary); ok { 2597 switch binary.Op { 2598 case BinOpStrictEq, BinOpStrictNe, BinOpLooseEq, BinOpLooseNe: 2599 // Pattern match for "typeof x !== <string>" 2600 typeof, string := binary.Left, binary.Right 2601 if _, ok := typeof.Data.(*EString); ok { 2602 typeof, string = string, typeof 2603 } 2604 if typeof, ok := typeof.Data.(*EUnary); ok && typeof.Op == UnOpTypeof && typeof.WasOriginallyTypeofIdentifier { 2605 if text, ok := string.Data.(*EString); ok { 2606 // In "typeof x !== 'undefined' ? x : null", the reference to "x" is side-effect free 2607 // In "typeof x === 'object' ? x : null", the reference to "x" is side-effect free 2608 if (helpers.UTF16EqualsString(text.Value, "undefined") == isYesBranch) == 2609 (binary.Op == BinOpStrictNe || binary.Op == BinOpLooseNe) { 2610 if id2, ok := typeof.Value.Data.(*EIdentifier); ok && id2.Ref == id.Ref { 2611 return true 2612 } 2613 } 2614 } 2615 } 2616 2617 case BinOpLt, BinOpGt, BinOpLe, BinOpGe: 2618 // Pattern match for "typeof x < <string>" 2619 typeof, string := binary.Left, binary.Right 2620 if _, ok := typeof.Data.(*EString); ok { 2621 typeof, string = string, typeof 2622 isYesBranch = !isYesBranch 2623 } 2624 if typeof, ok := typeof.Data.(*EUnary); ok && typeof.Op == UnOpTypeof && typeof.WasOriginallyTypeofIdentifier { 2625 if text, ok := string.Data.(*EString); ok && helpers.UTF16EqualsString(text.Value, "u") { 2626 // In "typeof x < 'u' ? x : null", the reference to "x" is side-effect free 2627 // In "typeof x > 'u' ? x : null", the reference to "x" is side-effect free 2628 if isYesBranch == (binary.Op == BinOpLt || binary.Op == BinOpLe) { 2629 if id2, ok := typeof.Value.Data.(*EIdentifier); ok && id2.Ref == id.Ref { 2630 return true 2631 } 2632 } 2633 } 2634 } 2635 } 2636 } 2637 } 2638 return false 2639 } 2640 2641 func StringToEquivalentNumberValue(value []uint16) (float64, bool) { 2642 if len(value) > 0 { 2643 var intValue int32 2644 isNegative := false 2645 start := 0 2646 2647 if value[0] == '-' && len(value) > 1 { 2648 isNegative = true 2649 start++ 2650 } 2651 2652 for _, c := range value[start:] { 2653 if c < '0' || c > '9' { 2654 return 0, false 2655 } 2656 intValue = intValue*10 + int32(c) - '0' 2657 } 2658 2659 if isNegative { 2660 intValue = -intValue 2661 } 2662 2663 if helpers.UTF16EqualsString(value, strconv.FormatInt(int64(intValue), 10)) { 2664 return float64(intValue), true 2665 } 2666 } 2667 2668 return 0, false 2669 } 2670 2671 // This function intentionally avoids mutating the input AST so it can be 2672 // called after the AST has been frozen (i.e. after parsing ends). 2673 func InlineSpreadsOfArrayLiterals(values []Expr) (results []Expr) { 2674 for _, value := range values { 2675 if spread, ok := value.Data.(*ESpread); ok { 2676 if array, ok := spread.Value.Data.(*EArray); ok { 2677 for _, item := range array.Items { 2678 if _, ok := item.Data.(*EMissing); ok { 2679 results = append(results, Expr{Loc: item.Loc, Data: EUndefinedShared}) 2680 } else { 2681 results = append(results, item) 2682 } 2683 } 2684 continue 2685 } 2686 } 2687 results = append(results, value) 2688 } 2689 return 2690 } 2691 2692 // This function intentionally avoids mutating the input AST so it can be 2693 // called after the AST has been frozen (i.e. after parsing ends). 2694 func MangleObjectSpread(properties []Property) []Property { 2695 var result []Property 2696 for _, property := range properties { 2697 if property.Kind == PropertySpread { 2698 switch v := property.ValueOrNil.Data.(type) { 2699 case *EBoolean, *ENull, *EUndefined, *ENumber, 2700 *EBigInt, *ERegExp, *EFunction, *EArrow: 2701 // This value is ignored because it doesn't have any of its own properties 2702 continue 2703 2704 case *EObject: 2705 for i, p := range v.Properties { 2706 // Getters are evaluated at iteration time. The property 2707 // descriptor is not inlined into the caller. Since we are not 2708 // evaluating code at compile time, just bail if we hit one 2709 // and preserve the spread with the remaining properties. 2710 if p.Kind == PropertyGetter || p.Kind == PropertySetter { 2711 // Don't mutate the original AST 2712 clone := *v 2713 clone.Properties = v.Properties[i:] 2714 property.ValueOrNil.Data = &clone 2715 result = append(result, property) 2716 break 2717 } 2718 2719 // Also bail if we hit a verbatim "__proto__" key. This will 2720 // actually set the prototype of the object being spread so 2721 // inlining it is not correct. 2722 if p.Kind == PropertyField && !p.Flags.Has(PropertyIsComputed) { 2723 if str, ok := p.Key.Data.(*EString); ok && helpers.UTF16EqualsString(str.Value, "__proto__") { 2724 // Don't mutate the original AST 2725 clone := *v 2726 clone.Properties = v.Properties[i:] 2727 property.ValueOrNil.Data = &clone 2728 result = append(result, property) 2729 break 2730 } 2731 } 2732 2733 result = append(result, p) 2734 } 2735 continue 2736 } 2737 } 2738 result = append(result, property) 2739 } 2740 return result 2741 } 2742 2743 // This function intentionally avoids mutating the input AST so it can be 2744 // called after the AST has been frozen (i.e. after parsing ends). 2745 func (ctx HelperContext) MangleIfExpr(loc logger.Loc, e *EIf, unsupportedFeatures compat.JSFeature) Expr { 2746 test := e.Test 2747 yes := e.Yes 2748 no := e.No 2749 2750 // "(a, b) ? c : d" => "a, b ? c : d" 2751 if comma, ok := test.Data.(*EBinary); ok && comma.Op == BinOpComma { 2752 return JoinWithComma(comma.Left, ctx.MangleIfExpr(comma.Right.Loc, &EIf{ 2753 Test: comma.Right, 2754 Yes: yes, 2755 No: no, 2756 }, unsupportedFeatures)) 2757 } 2758 2759 // "!a ? b : c" => "a ? c : b" 2760 if not, ok := test.Data.(*EUnary); ok && not.Op == UnOpNot { 2761 test = not.Value 2762 yes, no = no, yes 2763 } 2764 2765 if ValuesLookTheSame(yes.Data, no.Data) { 2766 // "/* @__PURE__ */ a() ? b : b" => "b" 2767 if ctx.ExprCanBeRemovedIfUnused(test) { 2768 return yes 2769 } 2770 2771 // "a ? b : b" => "a, b" 2772 return JoinWithComma(test, yes) 2773 } 2774 2775 // "a ? true : false" => "!!a" 2776 // "a ? false : true" => "!a" 2777 if y, ok := yes.Data.(*EBoolean); ok { 2778 if n, ok := no.Data.(*EBoolean); ok { 2779 if y.Value && !n.Value { 2780 return Not(Not(test)) 2781 } 2782 if !y.Value && n.Value { 2783 return Not(test) 2784 } 2785 } 2786 } 2787 2788 if id, ok := test.Data.(*EIdentifier); ok { 2789 // "a ? a : b" => "a || b" 2790 if id2, ok := yes.Data.(*EIdentifier); ok && id.Ref == id2.Ref { 2791 return JoinWithLeftAssociativeOp(BinOpLogicalOr, test, no) 2792 } 2793 2794 // "a ? b : a" => "a && b" 2795 if id2, ok := no.Data.(*EIdentifier); ok && id.Ref == id2.Ref { 2796 return JoinWithLeftAssociativeOp(BinOpLogicalAnd, test, yes) 2797 } 2798 } 2799 2800 // "a ? b ? c : d : d" => "a && b ? c : d" 2801 if yesIf, ok := yes.Data.(*EIf); ok && ValuesLookTheSame(yesIf.No.Data, no.Data) { 2802 return Expr{Loc: loc, Data: &EIf{Test: JoinWithLeftAssociativeOp(BinOpLogicalAnd, test, yesIf.Test), Yes: yesIf.Yes, No: no}} 2803 } 2804 2805 // "a ? b : c ? b : d" => "a || c ? b : d" 2806 if noIf, ok := no.Data.(*EIf); ok && ValuesLookTheSame(yes.Data, noIf.Yes.Data) { 2807 return Expr{Loc: loc, Data: &EIf{Test: JoinWithLeftAssociativeOp(BinOpLogicalOr, test, noIf.Test), Yes: yes, No: noIf.No}} 2808 } 2809 2810 // "a ? c : (b, c)" => "(a || b), c" 2811 if comma, ok := no.Data.(*EBinary); ok && comma.Op == BinOpComma && ValuesLookTheSame(yes.Data, comma.Right.Data) { 2812 return JoinWithComma( 2813 JoinWithLeftAssociativeOp(BinOpLogicalOr, test, comma.Left), 2814 comma.Right, 2815 ) 2816 } 2817 2818 // "a ? (b, c) : c" => "(a && b), c" 2819 if comma, ok := yes.Data.(*EBinary); ok && comma.Op == BinOpComma && ValuesLookTheSame(comma.Right.Data, no.Data) { 2820 return JoinWithComma( 2821 JoinWithLeftAssociativeOp(BinOpLogicalAnd, test, comma.Left), 2822 comma.Right, 2823 ) 2824 } 2825 2826 // "a ? b || c : c" => "(a && b) || c" 2827 if binary, ok := yes.Data.(*EBinary); ok && binary.Op == BinOpLogicalOr && 2828 ValuesLookTheSame(binary.Right.Data, no.Data) { 2829 return Expr{Loc: loc, Data: &EBinary{ 2830 Op: BinOpLogicalOr, 2831 Left: JoinWithLeftAssociativeOp(BinOpLogicalAnd, test, binary.Left), 2832 Right: binary.Right, 2833 }} 2834 } 2835 2836 // "a ? c : b && c" => "(a || b) && c" 2837 if binary, ok := no.Data.(*EBinary); ok && binary.Op == BinOpLogicalAnd && 2838 ValuesLookTheSame(yes.Data, binary.Right.Data) { 2839 return Expr{Loc: loc, Data: &EBinary{ 2840 Op: BinOpLogicalAnd, 2841 Left: JoinWithLeftAssociativeOp(BinOpLogicalOr, test, binary.Left), 2842 Right: binary.Right, 2843 }} 2844 } 2845 2846 // "a ? b(c, d) : b(e, d)" => "b(a ? c : e, d)" 2847 if y, ok := yes.Data.(*ECall); ok && len(y.Args) > 0 { 2848 if n, ok := no.Data.(*ECall); ok && len(n.Args) == len(y.Args) && 2849 y.HasSameFlagsAs(n) && ValuesLookTheSame(y.Target.Data, n.Target.Data) { 2850 // Only do this if the condition can be reordered past the call target 2851 // without side effects. For example, if the test or the call target is 2852 // an unbound identifier, reordering could potentially mean evaluating 2853 // the code could throw a different ReferenceError. 2854 if ctx.ExprCanBeRemovedIfUnused(test) && ctx.ExprCanBeRemovedIfUnused(y.Target) { 2855 sameTailArgs := true 2856 for i, count := 1, len(y.Args); i < count; i++ { 2857 if !ValuesLookTheSame(y.Args[i].Data, n.Args[i].Data) { 2858 sameTailArgs = false 2859 break 2860 } 2861 } 2862 if sameTailArgs { 2863 yesSpread, yesIsSpread := y.Args[0].Data.(*ESpread) 2864 noSpread, noIsSpread := n.Args[0].Data.(*ESpread) 2865 2866 // "a ? b(...c) : b(...e)" => "b(...a ? c : e)" 2867 if yesIsSpread && noIsSpread { 2868 // Don't mutate the original AST 2869 temp := EIf{Test: test, Yes: yesSpread.Value, No: noSpread.Value} 2870 clone := *y 2871 clone.Args = append([]Expr{}, clone.Args...) 2872 clone.Args[0] = Expr{Loc: loc, Data: &ESpread{Value: ctx.MangleIfExpr(loc, &temp, unsupportedFeatures)}} 2873 return Expr{Loc: loc, Data: &clone} 2874 } 2875 2876 // "a ? b(c) : b(e)" => "b(a ? c : e)" 2877 if !yesIsSpread && !noIsSpread { 2878 // Don't mutate the original AST 2879 temp := EIf{Test: test, Yes: y.Args[0], No: n.Args[0]} 2880 clone := *y 2881 clone.Args = append([]Expr{}, clone.Args...) 2882 clone.Args[0] = ctx.MangleIfExpr(loc, &temp, unsupportedFeatures) 2883 return Expr{Loc: loc, Data: &clone} 2884 } 2885 } 2886 } 2887 } 2888 } 2889 2890 // Try using the "??" or "?." operators 2891 if binary, ok := test.Data.(*EBinary); ok { 2892 var check Expr 2893 var whenNull Expr 2894 var whenNonNull Expr 2895 2896 switch binary.Op { 2897 case BinOpLooseEq: 2898 if _, ok := binary.Right.Data.(*ENull); ok { 2899 // "a == null ? _ : _" 2900 check = binary.Left 2901 whenNull = yes 2902 whenNonNull = no 2903 } else if _, ok := binary.Left.Data.(*ENull); ok { 2904 // "null == a ? _ : _" 2905 check = binary.Right 2906 whenNull = yes 2907 whenNonNull = no 2908 } 2909 2910 case BinOpLooseNe: 2911 if _, ok := binary.Right.Data.(*ENull); ok { 2912 // "a != null ? _ : _" 2913 check = binary.Left 2914 whenNonNull = yes 2915 whenNull = no 2916 } else if _, ok := binary.Left.Data.(*ENull); ok { 2917 // "null != a ? _ : _" 2918 check = binary.Right 2919 whenNonNull = yes 2920 whenNull = no 2921 } 2922 } 2923 2924 if ctx.ExprCanBeRemovedIfUnused(check) { 2925 // "a != null ? a : b" => "a ?? b" 2926 if !unsupportedFeatures.Has(compat.NullishCoalescing) && ValuesLookTheSame(check.Data, whenNonNull.Data) { 2927 return JoinWithLeftAssociativeOp(BinOpNullishCoalescing, check, whenNull) 2928 } 2929 2930 // "a != null ? a.b.c[d](e) : undefined" => "a?.b.c[d](e)" 2931 if !unsupportedFeatures.Has(compat.OptionalChain) { 2932 if _, ok := whenNull.Data.(*EUndefined); ok && TryToInsertOptionalChain(check, whenNonNull) { 2933 return whenNonNull 2934 } 2935 } 2936 } 2937 } 2938 2939 // Don't mutate the original AST 2940 if test != e.Test || yes != e.Yes || no != e.No { 2941 return Expr{Loc: loc, Data: &EIf{Test: test, Yes: yes, No: no}} 2942 } 2943 2944 return Expr{Loc: loc, Data: e} 2945 } 2946 2947 func ForEachIdentifierBindingInDecls(decls []Decl, callback func(loc logger.Loc, b *BIdentifier)) { 2948 for _, decl := range decls { 2949 ForEachIdentifierBinding(decl.Binding, callback) 2950 } 2951 } 2952 2953 func ForEachIdentifierBinding(binding Binding, callback func(loc logger.Loc, b *BIdentifier)) { 2954 switch b := binding.Data.(type) { 2955 case *BMissing: 2956 2957 case *BIdentifier: 2958 callback(binding.Loc, b) 2959 2960 case *BArray: 2961 for _, item := range b.Items { 2962 ForEachIdentifierBinding(item.Binding, callback) 2963 } 2964 2965 case *BObject: 2966 for _, property := range b.Properties { 2967 ForEachIdentifierBinding(property.Value, callback) 2968 } 2969 2970 default: 2971 panic("Internal error") 2972 } 2973 }