func JoinWithLeftAssociativeOp(op OpCode, a Expr, b Expr) Expr {
	if comma, ok := a.Data.(*EBinary); ok && comma.Op == BinOpComma {
		comma.Right = JoinWithLeftAssociativeOp(op, comma.Right, b)
		return a
	}
	if binary, ok := b.Data.(*EBinary); ok && binary.Op == op {
		return JoinWithLeftAssociativeOp(
			op,
			JoinWithLeftAssociativeOp(op, a, binary.Left),
			binary.Right,
		)
	}
	return Expr{Loc: a.Loc, Data: &EBinary{Op: op, Left: a, Right: b}}
}
The goal of this function is to "rotate" the AST if it's possible to use the
left-associative property of the operator to avoid unnecessary parentheses.

When using this, make absolutely sure that the operator is actually
associative. For example, the "-" operator is not associative for
floating-point numbers.
package js_ast
import (
	"math"
	"sort"
	"github.com/evanw/esbuild/internal/ast"
	"github.com/evanw/esbuild/internal/compat"
	"github.com/evanw/esbuild/internal/logger"
)
type L int
const (
	LLowest L = iota
	LComma
	LSpread
	LYield
	LAssign
	LConditional
	LNullishCoalescing
	LLogicalOr
	LLogicalAnd
	LBitwiseOr
	LBitwiseXor
	LBitwiseAnd
	LEquals
	LCompare
	LShift
	LAdd
	LMultiply
	LExponentiation
	LPrefix
	LPostfix
	LNew
	LCall
	LMember
)
type OpCode int
func (op OpCode) IsPrefix() bool {
	return op < UnOpPostDec
}
func (op OpCode) UnaryAssignTarget() AssignTarget {
	if op >= UnOpPreDec && op <= UnOpPostInc {
		return AssignTargetUpdate
	}
	return AssignTargetNone
}
func (op OpCode) IsLeftAssociative() bool {
	return op >= BinOpAdd && op < BinOpComma && op != BinOpPow
}
func (op OpCode) IsRightAssociative() bool {
	return op >= BinOpAssign || op == BinOpPow
}
func (op OpCode) BinaryAssignTarget() AssignTarget {
	if op == BinOpAssign {
		return AssignTargetReplace
	}
	if op > BinOpAssign {
		return AssignTargetUpdate
	}
	return AssignTargetNone
}
func (op OpCode) IsShortCircuit() bool {
	switch op {
	case BinOpLogicalOr, BinOpLogicalOrAssign,
		BinOpLogicalAnd, BinOpLogicalAndAssign,
		BinOpNullishCoalescing, BinOpNullishCoalescingAssign:
		return true
	}
	return false
}
type AssignTarget uint8
const (
	AssignTargetNone    AssignTarget = iota
	AssignTargetReplace               
	AssignTargetUpdate                
)
const (
	UnOpPos OpCode = iota
	UnOpNeg
	UnOpCpl
	UnOpNot
	UnOpVoid
	UnOpTypeof
	UnOpDelete
	UnOpPreDec
	UnOpPreInc
	UnOpPostDec
	UnOpPostInc
	BinOpAdd
	BinOpSub
	BinOpMul
	BinOpDiv
	BinOpRem
	BinOpPow
	BinOpLt
	BinOpLe
	BinOpGt
	BinOpGe
	BinOpIn
	BinOpInstanceof
	BinOpShl
	BinOpShr
	BinOpUShr
	BinOpLooseEq
	BinOpLooseNe
	BinOpStrictEq
	BinOpStrictNe
	BinOpNullishCoalescing
	BinOpLogicalOr
	BinOpLogicalAnd
	BinOpBitwiseOr
	BinOpBitwiseAnd
	BinOpBitwiseXor
	BinOpComma
	BinOpAssign
	BinOpAddAssign
	BinOpSubAssign
	BinOpMulAssign
	BinOpDivAssign
	BinOpRemAssign
	BinOpPowAssign
	BinOpShlAssign
	BinOpShrAssign
	BinOpUShrAssign
	BinOpBitwiseOrAssign
	BinOpBitwiseAndAssign
	BinOpBitwiseXorAssign
	BinOpNullishCoalescingAssign
	BinOpLogicalOrAssign
	BinOpLogicalAndAssign
)
type opTableEntry struct {
	Text      string
	Level     L
	IsKeyword bool
}
var OpTable = []opTableEntry{
	{"+", LPrefix, false},
	{"-", LPrefix, false},
	{"~", LPrefix, false},
	{"!", LPrefix, false},
	{"void", LPrefix, true},
	{"typeof", LPrefix, true},
	{"delete", LPrefix, true},
	{"--", LPrefix, false},
	{"++", LPrefix, false},
	{"--", LPostfix, false},
	{"++", LPostfix, false},
	{"+", LAdd, false},
	{"-", LAdd, false},
	{"*", LMultiply, false},
	{"/", LMultiply, false},
	{"%", LMultiply, false},
	{"**", LExponentiation, false},  
	{"<", LCompare, false},
	{"<=", LCompare, false},
	{">", LCompare, false},
	{">=", LCompare, false},
	{"in", LCompare, true},
	{"instanceof", LCompare, true},
	{"<<", LShift, false},
	{">>", LShift, false},
	{">>>", LShift, false},
	{"==", LEquals, false},
	{"!=", LEquals, false},
	{"===", LEquals, false},
	{"!==", LEquals, false},
	{"??", LNullishCoalescing, false},
	{"||", LLogicalOr, false},
	{"&&", LLogicalAnd, false},
	{"|", LBitwiseOr, false},
	{"&", LBitwiseAnd, false},
	{"^", LBitwiseXor, false},
	{",", LComma, false},
	{"=", LAssign, false},
	{"+=", LAssign, false},
	{"-=", LAssign, false},
	{"*=", LAssign, false},
	{"/=", LAssign, false},
	{"%=", LAssign, false},
	{"**=", LAssign, false},
	{"<<=", LAssign, false},
	{">>=", LAssign, false},
	{">>>=", LAssign, false},
	{"|=", LAssign, false},
	{"&=", LAssign, false},
	{"^=", LAssign, false},
	{"??=", LAssign, false},
	{"||=", LAssign, false},
	{"&&=", LAssign, false},
}
type LocRef struct {
	Loc logger.Loc
	Ref Ref
}
type Comment struct {
	Loc  logger.Loc
	Text string
}
type PropertyKind int
const (
	PropertyNormal PropertyKind = iota
	PropertyGet
	PropertySet
	PropertySpread
	PropertyDeclare
)
type Property struct {
	TSDecorators []Expr
	Key          Expr
	ValueOrNil Expr
	InitializerOrNil Expr
	Kind            PropertyKind
	IsComputed      bool
	IsMethod        bool
	IsStatic        bool
	WasShorthand    bool
	PreferQuotedKey bool
}
type PropertyBinding struct {
	Key               Expr
	Value             Binding
	DefaultValueOrNil Expr
	IsComputed        bool
	IsSpread          bool
	PreferQuotedKey   bool
}
type Arg struct {
	TSDecorators []Expr
	Binding      Binding
	DefaultOrNil Expr
	IsTypeScriptCtorField bool
}
type Fn struct {
	Name         *LocRef
	OpenParenLoc logger.Loc
	Args         []Arg
	Body         FnBody
	ArgumentsRef Ref
	IsAsync     bool
	IsGenerator bool
	HasRestArg  bool
	HasIfScope  bool
	IsUniqueFormalParameters bool
}
type FnBody struct {
	Loc   logger.Loc
	Stmts []Stmt
}
type Class struct {
	ClassKeyword logger.Range
	TSDecorators []Expr
	Name         *LocRef
	ExtendsOrNil Expr
	BodyLoc      logger.Loc
	Properties   []Property
}
type ArrayBinding struct {
	Binding           Binding
	DefaultValueOrNil Expr
}
type Binding struct {
	Loc  logger.Loc
	Data B
}
type B interface{ isBinding() }
type BMissing struct{}
type BIdentifier struct{ Ref Ref }
type BArray struct {
	Items        []ArrayBinding
	HasSpread    bool
	IsSingleLine bool
}
type BObject struct {
	Properties   []PropertyBinding
	IsSingleLine bool
}
func (*BMissing) isBinding()    {}
func (*BIdentifier) isBinding() {}
func (*BArray) isBinding()      {}
func (*BObject) isBinding()     {}
type Expr struct {
	Loc  logger.Loc
	Data E
}
type E interface{ isExpr() }
type EArray struct {
	Items            []Expr
	CommaAfterSpread logger.Loc
	IsSingleLine     bool
	IsParenthesized  bool
}
type EUnary struct {
	Op    OpCode
	Value Expr
}
type EBinary struct {
	Left  Expr
	Right Expr
	Op    OpCode
}
type EBoolean struct{ Value bool }
type EMissing struct{}
type ESuper struct{}
type ENull struct{}
type EUndefined struct{}
type EThis struct{}
type ENewTarget struct {
	Range logger.Range
}
type EImportMeta struct{}
var BMissingShared = &BMissing{}
var EMissingShared = &EMissing{}
var ESuperShared = &ESuper{}
var ENullShared = &ENull{}
var EUndefinedShared = &EUndefined{}
var EThisShared = &EThis{}
var EImportMetaShared = &EImportMeta{}
type ENew struct {
	Target Expr
	Args   []Expr
	CanBeUnwrappedIfUnused bool
}
type OptionalChain uint8
const (
	OptionalChainNone OptionalChain = iota
	OptionalChainStart
	OptionalChainContinue
)
type ECall struct {
	Target        Expr
	Args          []Expr
	OptionalChain OptionalChain
	IsDirectEval  bool
	CanBeUnwrappedIfUnused bool
}
func (a *ECall) HasSameFlagsAs(b *ECall) bool {
	return a.OptionalChain == b.OptionalChain &&
		a.IsDirectEval == b.IsDirectEval &&
		a.CanBeUnwrappedIfUnused == b.CanBeUnwrappedIfUnused
}
type EDot struct {
	Target        Expr
	Name          string
	NameLoc       logger.Loc
	OptionalChain OptionalChain
	CanBeRemovedIfUnused bool
	CallCanBeUnwrappedIfUnused bool
}
func (a *EDot) HasSameFlagsAs(b *EDot) bool {
	return a.OptionalChain == b.OptionalChain &&
		a.CanBeRemovedIfUnused == b.CanBeRemovedIfUnused &&
		a.CallCanBeUnwrappedIfUnused == b.CallCanBeUnwrappedIfUnused
}
type EIndex struct {
	Target        Expr
	Index         Expr
	OptionalChain OptionalChain
}
func (a *EIndex) HasSameFlagsAs(b *EIndex) bool {
	return a.OptionalChain == b.OptionalChain
}
type EArrow struct {
	Args []Arg
	Body FnBody
	IsAsync    bool
	HasRestArg bool
	PreferExpr bool  
}
type EFunction struct{ Fn Fn }
type EClass struct{ Class Class }
type EIdentifier struct {
	Ref Ref
	MustKeepDueToWithStmt bool
	CanBeRemovedIfUnused bool
	CallCanBeUnwrappedIfUnused bool
}
type EImportIdentifier struct {
	Ref             Ref
	PreferQuotedKey bool
	WasOriginallyIdentifier bool
}
type EPrivateIdentifier struct {
	Ref Ref
}
type EJSXElement struct {
	TagOrNil   Expr
	Properties []Property
	Children   []Expr
	CloseLoc   logger.Loc
}
type ENumber struct{ Value float64 }
type EBigInt struct{ Value string }
type EObject struct {
	Properties       []Property
	CommaAfterSpread logger.Loc
	IsSingleLine     bool
	IsParenthesized  bool
}
type ESpread struct{ Value Expr }
type EString struct {
	Value          []uint16
	LegacyOctalLoc logger.Loc
	PreferTemplate bool
}
type TemplatePart struct {
	Value      Expr
	TailLoc    logger.Loc
	TailCooked []uint16  
	TailRaw    string    
}
type ETemplate struct {
	TagOrNil       Expr
	HeadLoc        logger.Loc
	HeadCooked     []uint16  
	HeadRaw        string    
	Parts          []TemplatePart
	LegacyOctalLoc logger.Loc
}
type ERegExp struct{ Value string }
type EAwait struct {
	Value Expr
}
type EYield struct {
	ValueOrNil Expr
	IsStar     bool
}
type EIf struct {
	Test Expr
	Yes  Expr
	No   Expr
}
type ERequireString struct {
	ImportRecordIndex uint32
}
type ERequireResolveString struct {
	ImportRecordIndex uint32
}
type EImportString struct {
	ImportRecordIndex uint32
	LeadingInteriorComments []Comment
}
type EImportCall struct {
	Expr         Expr
	OptionsOrNil Expr
	LeadingInteriorComments []Comment
}
func (*EArray) isExpr()                {}
func (*EUnary) isExpr()                {}
func (*EBinary) isExpr()               {}
func (*EBoolean) isExpr()              {}
func (*ESuper) isExpr()                {}
func (*ENull) isExpr()                 {}
func (*EUndefined) isExpr()            {}
func (*EThis) isExpr()                 {}
func (*ENew) isExpr()                  {}
func (*ENewTarget) isExpr()            {}
func (*EImportMeta) isExpr()           {}
func (*ECall) isExpr()                 {}
func (*EDot) isExpr()                  {}
func (*EIndex) isExpr()                {}
func (*EArrow) isExpr()                {}
func (*EFunction) isExpr()             {}
func (*EClass) isExpr()                {}
func (*EIdentifier) isExpr()           {}
func (*EImportIdentifier) isExpr()     {}
func (*EPrivateIdentifier) isExpr()    {}
func (*EJSXElement) isExpr()           {}
func (*EMissing) isExpr()              {}
func (*ENumber) isExpr()               {}
func (*EBigInt) isExpr()               {}
func (*EObject) isExpr()               {}
func (*ESpread) isExpr()               {}
func (*EString) isExpr()               {}
func (*ETemplate) isExpr()             {}
func (*ERegExp) isExpr()               {}
func (*EAwait) isExpr()                {}
func (*EYield) isExpr()                {}
func (*EIf) isExpr()                   {}
func (*ERequireString) isExpr()        {}
func (*ERequireResolveString) isExpr() {}
func (*EImportString) isExpr()         {}
func (*EImportCall) isExpr()           {}
func IsOptionalChain(value Expr) bool {
	switch e := value.Data.(type) {
	case *EDot:
		return e.OptionalChain != OptionalChainNone
	case *EIndex:
		return e.OptionalChain != OptionalChainNone
	case *ECall:
		return e.OptionalChain != OptionalChainNone
	}
	return false
}
func Assign(a Expr, b Expr) Expr {
	return Expr{Loc: a.Loc, Data: &EBinary{Op: BinOpAssign, Left: a, Right: b}}
}
func AssignStmt(a Expr, b Expr) Stmt {
	return Stmt{Loc: a.Loc, Data: &SExpr{Value: Assign(a, b)}}
}
func Not(expr Expr) Expr {
	if result, ok := MaybeSimplifyNot(expr); ok {
		return result
	}
	return Expr{Loc: expr.Loc, Data: &EUnary{Op: UnOpNot, Value: expr}}
}
func MaybeSimplifyNot(expr Expr) (Expr, bool) {
	switch e := expr.Data.(type) {
	case *ENull, *EUndefined:
		return Expr{Loc: expr.Loc, Data: &EBoolean{Value: true}}, true
	case *EBoolean:
		return Expr{Loc: expr.Loc, Data: &EBoolean{Value: !e.Value}}, true
	case *ENumber:
		return Expr{Loc: expr.Loc, Data: &EBoolean{Value: e.Value == 0 || math.IsNaN(e.Value)}}, true
	case *EBigInt:
		return Expr{Loc: expr.Loc, Data: &EBoolean{Value: e.Value == "0"}}, true
	case *EString:
		return Expr{Loc: expr.Loc, Data: &EBoolean{Value: len(e.Value) == 0}}, true
	case *EFunction, *EArrow, *ERegExp:
		return Expr{Loc: expr.Loc, Data: &EBoolean{Value: false}}, true
	case *EUnary:
		if e.Op == UnOpNot && IsBooleanValue(e.Value) {
			return e.Value, true
		}
	case *EBinary:
		switch e.Op {
		case BinOpLooseEq:
			e.Op = BinOpLooseNe
			return expr, true
		case BinOpLooseNe:
			e.Op = BinOpLooseEq
			return expr, true
		case BinOpStrictEq:
			e.Op = BinOpStrictNe
			return expr, true
		case BinOpStrictNe:
			e.Op = BinOpStrictEq
			return expr, true
		case BinOpComma:
			e.Right = Not(e.Right)
			return expr, true
		}
	}
	return Expr{}, false
}
func IsBooleanValue(a Expr) bool {
	switch e := a.Data.(type) {
	case *EBoolean:
		return true
	case *EIf:
		return IsBooleanValue(e.Yes) && IsBooleanValue(e.No)
	case *EUnary:
		return e.Op == UnOpNot || e.Op == UnOpDelete
	case *EBinary:
		switch e.Op {
		case BinOpStrictEq, BinOpStrictNe, BinOpLooseEq, BinOpLooseNe,
			BinOpLt, BinOpGt, BinOpLe, BinOpGe,
			BinOpInstanceof, BinOpIn:
			return true
		case BinOpLogicalOr, BinOpLogicalAnd:
			return IsBooleanValue(e.Left) && IsBooleanValue(e.Right)
		case BinOpNullishCoalescing:
			return IsBooleanValue(e.Left)
		}
	}
	return false
}
func IsNumericValue(a Expr) bool {
	switch e := a.Data.(type) {
	case *ENumber:
		return true
	case *EIf:
		return IsNumericValue(e.Yes) && IsNumericValue(e.No)
	case *EUnary:
		switch e.Op {
		case UnOpPos, UnOpNeg, UnOpCpl, UnOpPreDec, UnOpPreInc, UnOpPostDec, UnOpPostInc:
			return true
		}
	case *EBinary:
		switch e.Op {
		case BinOpAdd:
			return IsNumericValue(e.Left) && IsNumericValue(e.Right)
		case
			BinOpSub, BinOpSubAssign,
			BinOpMul, BinOpMulAssign,
			BinOpDiv, BinOpDivAssign,
			BinOpRem, BinOpRemAssign,
			BinOpBitwiseAnd, BinOpBitwiseAndAssign,
			BinOpBitwiseOr, BinOpBitwiseOrAssign,
			BinOpBitwiseXor, BinOpBitwiseXorAssign,
			BinOpShl, BinOpShlAssign,
			BinOpShr, BinOpShrAssign,
			BinOpUShr, BinOpUShrAssign:
			return true
		case BinOpAssign, BinOpComma:
			return IsNumericValue(e.Right)
		}
	}
	return false
}
func IsStringValue(a Expr) bool {
	switch e := a.Data.(type) {
	case *EString:
		return true
	case *ETemplate:
		return e.TagOrNil.Data == nil
	case *EIf:
		return IsStringValue(e.Yes) && IsStringValue(e.No)
	case *EUnary:
		return e.Op == UnOpTypeof
	case *EBinary:
		switch e.Op {
		case BinOpAdd:
			return IsStringValue(e.Left) || IsStringValue(e.Right)
		case BinOpAssign, BinOpAddAssign, BinOpComma:
			return IsNumericValue(e.Right)
		}
	}
	return false
}