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  }