1package js
   2
   3import (
   4	"bytes"
   5	"encoding/hex"
   6	stdStrconv "strconv"
   7	"unicode/utf8"
   8
   9	"github.com/tdewolff/minify/v2"
  10	"github.com/tdewolff/parse/v2/js"
  11	"github.com/tdewolff/parse/v2/strconv"
  12)
  13
  14var (
  15	spaceBytes                 = []byte(" ")
  16	newlineBytes               = []byte("\n")
  17	starBytes                  = []byte("*")
  18	colonBytes                 = []byte(":")
  19	semicolonBytes             = []byte(";")
  20	commaBytes                 = []byte(",")
  21	dotBytes                   = []byte(".")
  22	ellipsisBytes              = []byte("...")
  23	openBraceBytes             = []byte("{")
  24	closeBraceBytes            = []byte("}")
  25	openParenBytes             = []byte("(")
  26	closeParenBytes            = []byte(")")
  27	openBracketBytes           = []byte("[")
  28	closeBracketBytes          = []byte("]")
  29	openParenBracketBytes      = []byte("({")
  30	closeParenOpenBracketBytes = []byte("){")
  31	notBytes                   = []byte("!")
  32	questionBytes              = []byte("?")
  33	equalBytes                 = []byte("=")
  34	optChainBytes              = []byte("?.")
  35	arrowBytes                 = []byte("=>")
  36	zeroBytes                  = []byte("0")
  37	oneBytes                   = []byte("1")
  38	letBytes                   = []byte("let")
  39	getBytes                   = []byte("get")
  40	setBytes                   = []byte("set")
  41	asyncBytes                 = []byte("async")
  42	functionBytes              = []byte("function")
  43	staticBytes                = []byte("static")
  44	ifOpenBytes                = []byte("if(")
  45	elseBytes                  = []byte("else")
  46	withOpenBytes              = []byte("with(")
  47	doBytes                    = []byte("do")
  48	whileOpenBytes             = []byte("while(")
  49	forOpenBytes               = []byte("for(")
  50	forAwaitOpenBytes          = []byte("for await(")
  51	inBytes                    = []byte("in")
  52	ofBytes                    = []byte("of")
  53	switchOpenBytes            = []byte("switch(")
  54	throwBytes                 = []byte("throw")
  55	tryBytes                   = []byte("try")
  56	catchBytes                 = []byte("catch")
  57	finallyBytes               = []byte("finally")
  58	importBytes                = []byte("import")
  59	exportBytes                = []byte("export")
  60	fromBytes                  = []byte("from")
  61	returnBytes                = []byte("return")
  62	classBytes                 = []byte("class")
  63	asSpaceBytes               = []byte("as ")
  64	asyncSpaceBytes            = []byte("async ")
  65	spaceDefaultBytes          = []byte(" default")
  66	spaceExtendsBytes          = []byte(" extends")
  67	yieldBytes                 = []byte("yield")
  68	newBytes                   = []byte("new")
  69	openNewBytes               = []byte("(new")
  70	newTargetBytes             = []byte("new.target")
  71	importMetaBytes            = []byte("import.meta")
  72	nanBytes                   = []byte("NaN")
  73	undefinedBytes             = []byte("undefined")
  74	infinityBytes              = []byte("Infinity")
  75	nullBytes                  = []byte("null")
  76	voidZeroBytes              = []byte("void 0")
  77	groupedVoidZeroBytes       = []byte("(void 0)")
  78	oneDivZeroBytes            = []byte("1/0")
  79	groupedOneDivZeroBytes     = []byte("(1/0)")
  80	notZeroBytes               = []byte("!0")
  81	groupedNotZeroBytes        = []byte("(!0)")
  82	notOneBytes                = []byte("!1")
  83	groupedNotOneBytes         = []byte("(!1)")
  84	debuggerBytes              = []byte("debugger")
  85	regExpScriptBytes          = []byte("/script>")
  86)
  87
  88func isEmptyStmt(stmt js.IStmt) bool {
  89	if stmt == nil {
  90		return true
  91	} else if _, ok := stmt.(*js.EmptyStmt); ok {
  92		return true
  93	} else if decl, ok := stmt.(*js.VarDecl); ok && decl.TokenType == js.ErrorToken {
  94		for _, item := range decl.List {
  95			if item.Default != nil {
  96				return false
  97			}
  98		}
  99		return true
 100	} else if block, ok := stmt.(*js.BlockStmt); ok {
 101		for _, item := range block.List {
 102			if ok := isEmptyStmt(item); !ok {
 103				return false
 104			}
 105		}
 106		return true
 107	}
 108	return false
 109}
 110
 111func isFlowStmt(stmt js.IStmt) bool {
 112	if _, ok := stmt.(*js.ReturnStmt); ok {
 113		return true
 114	} else if _, ok := stmt.(*js.ThrowStmt); ok {
 115		return true
 116	} else if _, ok := stmt.(*js.BranchStmt); ok {
 117		return true
 118	}
 119	return false
 120}
 121
 122func lastStmt(stmt js.IStmt) js.IStmt {
 123	if block, ok := stmt.(*js.BlockStmt); ok && 0 < len(block.List) {
 124		return lastStmt(block.List[len(block.List)-1])
 125	}
 126	return stmt
 127}
 128
 129func endsInIf(istmt js.IStmt) bool {
 130	switch stmt := istmt.(type) {
 131	case *js.IfStmt:
 132		if stmt.Else == nil {
 133			_, ok := optimizeStmt(stmt).(*js.IfStmt)
 134			return ok
 135		}
 136		return endsInIf(stmt.Else)
 137	case *js.BlockStmt:
 138		if 0 < len(stmt.List) {
 139			return endsInIf(stmt.List[len(stmt.List)-1])
 140		}
 141	case *js.LabelledStmt:
 142		return endsInIf(stmt.Value)
 143	case *js.WithStmt:
 144		return endsInIf(stmt.Body)
 145	case *js.WhileStmt:
 146		return endsInIf(stmt.Body)
 147	case *js.ForStmt:
 148		return endsInIf(stmt.Body)
 149	case *js.ForInStmt:
 150		return endsInIf(stmt.Body)
 151	case *js.ForOfStmt:
 152		return endsInIf(stmt.Body)
 153	}
 154	return false
 155}
 156
 157// precedence maps for the precedence inside the operation
 158var unaryPrecMap = map[js.TokenType]js.OpPrec{
 159	js.PostIncrToken: js.OpLHS,
 160	js.PostDecrToken: js.OpLHS,
 161	js.PreIncrToken:  js.OpUnary,
 162	js.PreDecrToken:  js.OpUnary,
 163	js.NotToken:      js.OpUnary,
 164	js.BitNotToken:   js.OpUnary,
 165	js.TypeofToken:   js.OpUnary,
 166	js.VoidToken:     js.OpUnary,
 167	js.DeleteToken:   js.OpUnary,
 168	js.PosToken:      js.OpUnary,
 169	js.NegToken:      js.OpUnary,
 170	js.AwaitToken:    js.OpUnary,
 171}
 172
 173var binaryLeftPrecMap = map[js.TokenType]js.OpPrec{
 174	js.EqToken:         js.OpLHS,
 175	js.MulEqToken:      js.OpLHS,
 176	js.DivEqToken:      js.OpLHS,
 177	js.ModEqToken:      js.OpLHS,
 178	js.ExpEqToken:      js.OpLHS,
 179	js.AddEqToken:      js.OpLHS,
 180	js.SubEqToken:      js.OpLHS,
 181	js.LtLtEqToken:     js.OpLHS,
 182	js.GtGtEqToken:     js.OpLHS,
 183	js.GtGtGtEqToken:   js.OpLHS,
 184	js.BitAndEqToken:   js.OpLHS,
 185	js.BitXorEqToken:   js.OpLHS,
 186	js.BitOrEqToken:    js.OpLHS,
 187	js.ExpToken:        js.OpUpdate,
 188	js.MulToken:        js.OpMul,
 189	js.DivToken:        js.OpMul,
 190	js.ModToken:        js.OpMul,
 191	js.AddToken:        js.OpAdd,
 192	js.SubToken:        js.OpAdd,
 193	js.LtLtToken:       js.OpShift,
 194	js.GtGtToken:       js.OpShift,
 195	js.GtGtGtToken:     js.OpShift,
 196	js.LtToken:         js.OpCompare,
 197	js.LtEqToken:       js.OpCompare,
 198	js.GtToken:         js.OpCompare,
 199	js.GtEqToken:       js.OpCompare,
 200	js.InToken:         js.OpCompare,
 201	js.InstanceofToken: js.OpCompare,
 202	js.EqEqToken:       js.OpEquals,
 203	js.NotEqToken:      js.OpEquals,
 204	js.EqEqEqToken:     js.OpEquals,
 205	js.NotEqEqToken:    js.OpEquals,
 206	js.BitAndToken:     js.OpBitAnd,
 207	js.BitXorToken:     js.OpBitXor,
 208	js.BitOrToken:      js.OpBitOr,
 209	js.AndToken:        js.OpAnd,
 210	js.OrToken:         js.OpOr,
 211	js.NullishToken:    js.OpBitOr, // or OpCoalesce
 212	js.CommaToken:      js.OpExpr,
 213}
 214
 215var binaryRightPrecMap = map[js.TokenType]js.OpPrec{
 216	js.EqToken:         js.OpAssign,
 217	js.MulEqToken:      js.OpAssign,
 218	js.DivEqToken:      js.OpAssign,
 219	js.ModEqToken:      js.OpAssign,
 220	js.ExpEqToken:      js.OpAssign,
 221	js.AddEqToken:      js.OpAssign,
 222	js.SubEqToken:      js.OpAssign,
 223	js.LtLtEqToken:     js.OpAssign,
 224	js.GtGtEqToken:     js.OpAssign,
 225	js.GtGtGtEqToken:   js.OpAssign,
 226	js.BitAndEqToken:   js.OpAssign,
 227	js.BitXorEqToken:   js.OpAssign,
 228	js.BitOrEqToken:    js.OpAssign,
 229	js.ExpToken:        js.OpExp,
 230	js.MulToken:        js.OpExp,
 231	js.DivToken:        js.OpExp,
 232	js.ModToken:        js.OpExp,
 233	js.AddToken:        js.OpMul,
 234	js.SubToken:        js.OpMul,
 235	js.LtLtToken:       js.OpAdd,
 236	js.GtGtToken:       js.OpAdd,
 237	js.GtGtGtToken:     js.OpAdd,
 238	js.LtToken:         js.OpShift,
 239	js.LtEqToken:       js.OpShift,
 240	js.GtToken:         js.OpShift,
 241	js.GtEqToken:       js.OpShift,
 242	js.InToken:         js.OpShift,
 243	js.InstanceofToken: js.OpShift,
 244	js.EqEqToken:       js.OpCompare,
 245	js.NotEqToken:      js.OpCompare,
 246	js.EqEqEqToken:     js.OpCompare,
 247	js.NotEqEqToken:    js.OpCompare,
 248	js.BitAndToken:     js.OpEquals,
 249	js.BitXorToken:     js.OpBitAnd,
 250	js.BitOrToken:      js.OpBitXor,
 251	js.AndToken:        js.OpAnd,   // changes order in AST but not in execution
 252	js.OrToken:         js.OpOr,    // changes order in AST but not in execution
 253	js.NullishToken:    js.OpBitOr, // or OpCoalesce
 254	js.CommaToken:      js.OpAssign,
 255}
 256
 257// precedence maps of the operation itself
 258var unaryOpPrecMap = map[js.TokenType]js.OpPrec{
 259	js.PostIncrToken: js.OpUpdate,
 260	js.PostDecrToken: js.OpUpdate,
 261	js.PreIncrToken:  js.OpUpdate,
 262	js.PreDecrToken:  js.OpUpdate,
 263	js.NotToken:      js.OpUnary,
 264	js.BitNotToken:   js.OpUnary,
 265	js.TypeofToken:   js.OpUnary,
 266	js.VoidToken:     js.OpUnary,
 267	js.DeleteToken:   js.OpUnary,
 268	js.PosToken:      js.OpUnary,
 269	js.NegToken:      js.OpUnary,
 270	js.AwaitToken:    js.OpUnary,
 271}
 272
 273var binaryOpPrecMap = map[js.TokenType]js.OpPrec{
 274	js.EqToken:         js.OpAssign,
 275	js.MulEqToken:      js.OpAssign,
 276	js.DivEqToken:      js.OpAssign,
 277	js.ModEqToken:      js.OpAssign,
 278	js.ExpEqToken:      js.OpAssign,
 279	js.AddEqToken:      js.OpAssign,
 280	js.SubEqToken:      js.OpAssign,
 281	js.LtLtEqToken:     js.OpAssign,
 282	js.GtGtEqToken:     js.OpAssign,
 283	js.GtGtGtEqToken:   js.OpAssign,
 284	js.BitAndEqToken:   js.OpAssign,
 285	js.BitXorEqToken:   js.OpAssign,
 286	js.BitOrEqToken:    js.OpAssign,
 287	js.ExpToken:        js.OpExp,
 288	js.MulToken:        js.OpMul,
 289	js.DivToken:        js.OpMul,
 290	js.ModToken:        js.OpMul,
 291	js.AddToken:        js.OpAdd,
 292	js.SubToken:        js.OpAdd,
 293	js.LtLtToken:       js.OpShift,
 294	js.GtGtToken:       js.OpShift,
 295	js.GtGtGtToken:     js.OpShift,
 296	js.LtToken:         js.OpCompare,
 297	js.LtEqToken:       js.OpCompare,
 298	js.GtToken:         js.OpCompare,
 299	js.GtEqToken:       js.OpCompare,
 300	js.InToken:         js.OpCompare,
 301	js.InstanceofToken: js.OpCompare,
 302	js.EqEqToken:       js.OpEquals,
 303	js.NotEqToken:      js.OpEquals,
 304	js.EqEqEqToken:     js.OpEquals,
 305	js.NotEqEqToken:    js.OpEquals,
 306	js.BitAndToken:     js.OpBitAnd,
 307	js.BitXorToken:     js.OpBitXor,
 308	js.BitOrToken:      js.OpBitOr,
 309	js.AndToken:        js.OpAnd,
 310	js.OrToken:         js.OpOr,
 311	js.NullishToken:    js.OpCoalesce,
 312	js.CommaToken:      js.OpExpr,
 313}
 314
 315func exprPrec(i js.IExpr) js.OpPrec {
 316	switch expr := i.(type) {
 317	case *js.Var, *js.LiteralExpr, *js.ArrayExpr, *js.ObjectExpr, *js.FuncDecl, *js.ClassDecl:
 318		return js.OpPrimary
 319	case *js.UnaryExpr:
 320		return unaryOpPrecMap[expr.Op]
 321	case *js.BinaryExpr:
 322		return binaryOpPrecMap[expr.Op]
 323	case *js.NewExpr:
 324		if expr.Args == nil {
 325			return js.OpNew
 326		}
 327		return js.OpMember
 328	case *js.TemplateExpr:
 329		if expr.Tag == nil {
 330			return js.OpPrimary
 331		}
 332		return expr.Prec
 333	case *js.DotExpr:
 334		return expr.Prec
 335	case *js.IndexExpr:
 336		return expr.Prec
 337	case *js.NewTargetExpr, *js.ImportMetaExpr:
 338		return js.OpMember
 339	case *js.CallExpr:
 340		return js.OpCall
 341	case *js.CondExpr, *js.YieldExpr, *js.ArrowFunc:
 342		return js.OpAssign
 343	case *js.GroupExpr:
 344		return exprPrec(expr.X)
 345	}
 346	return js.OpExpr // CommaExpr
 347}
 348
 349func hasSideEffects(i js.IExpr) bool {
 350	// assume that variable usage and that the index operator themselves have no side effects
 351	switch expr := i.(type) {
 352	case *js.Var, *js.LiteralExpr, *js.FuncDecl, *js.ClassDecl, *js.ArrowFunc, *js.NewTargetExpr, *js.ImportMetaExpr:
 353		return false
 354	case *js.NewExpr, *js.CallExpr, *js.YieldExpr:
 355		return true
 356	case *js.GroupExpr:
 357		return hasSideEffects(expr.X)
 358	case *js.DotExpr:
 359		return hasSideEffects(expr.X)
 360	case *js.IndexExpr:
 361		return hasSideEffects(expr.X) || hasSideEffects(expr.Y)
 362	case *js.CondExpr:
 363		return hasSideEffects(expr.Cond) || hasSideEffects(expr.X) || hasSideEffects(expr.Y)
 364	case *js.CommaExpr:
 365		for _, item := range expr.List {
 366			if hasSideEffects(item) {
 367				return true
 368			}
 369		}
 370	case *js.ArrayExpr:
 371		for _, item := range expr.List {
 372			if hasSideEffects(item.Value) {
 373				return true
 374			}
 375		}
 376		return false
 377	case *js.ObjectExpr:
 378		for _, item := range expr.List {
 379			if hasSideEffects(item.Value) || item.Init != nil && hasSideEffects(item.Init) || item.Name != nil && item.Name.IsComputed() && hasSideEffects(item.Name.Computed) {
 380				return true
 381			}
 382		}
 383		return false
 384	case *js.TemplateExpr:
 385		if hasSideEffects(expr.Tag) {
 386			return true
 387		}
 388		for _, item := range expr.List {
 389			if hasSideEffects(item.Expr) {
 390				return true
 391			}
 392		}
 393		return false
 394	case *js.UnaryExpr:
 395		if expr.Op == js.DeleteToken || expr.Op == js.PreIncrToken || expr.Op == js.PreDecrToken || expr.Op == js.PostIncrToken || expr.Op == js.PostDecrToken {
 396			return true
 397		}
 398		return hasSideEffects(expr.X)
 399	case *js.BinaryExpr:
 400		return binaryOpPrecMap[expr.Op] == js.OpAssign
 401	}
 402	return true
 403}
 404
 405// TODO: use in more cases
 406func groupExpr(i js.IExpr, prec js.OpPrec) js.IExpr {
 407	precInside := exprPrec(i)
 408	if _, ok := i.(*js.GroupExpr); !ok && precInside < prec && (precInside != js.OpCoalesce || prec != js.OpBitOr) {
 409		return &js.GroupExpr{X: i}
 410	}
 411	return i
 412}
 413
 414// TODO: use in more cases
 415func condExpr(cond, x, y js.IExpr) js.IExpr {
 416	if comma, ok := cond.(*js.CommaExpr); ok {
 417		comma.List[len(comma.List)-1] = &js.CondExpr{
 418			Cond: groupExpr(comma.List[len(comma.List)-1], js.OpCoalesce),
 419			X:    groupExpr(x, js.OpAssign),
 420			Y:    groupExpr(y, js.OpAssign),
 421		}
 422		return comma
 423	}
 424	return &js.CondExpr{
 425		Cond: groupExpr(cond, js.OpCoalesce),
 426		X:    groupExpr(x, js.OpAssign),
 427		Y:    groupExpr(y, js.OpAssign),
 428	}
 429}
 430
 431func commaExpr(x, y js.IExpr) js.IExpr {
 432	comma, ok := x.(*js.CommaExpr)
 433	if !ok {
 434		comma = &js.CommaExpr{List: []js.IExpr{x}}
 435	}
 436	if comma2, ok := y.(*js.CommaExpr); ok {
 437		comma.List = append(comma.List, comma2.List...)
 438	} else {
 439		comma.List = append(comma.List, y)
 440	}
 441	return comma
 442}
 443
 444func innerExpr(i js.IExpr) js.IExpr {
 445	for {
 446		if group, ok := i.(*js.GroupExpr); ok {
 447			i = group.X
 448		} else {
 449			return i
 450		}
 451	}
 452}
 453
 454func finalExpr(i js.IExpr) js.IExpr {
 455	i = innerExpr(i)
 456	if comma, ok := i.(*js.CommaExpr); ok {
 457		i = comma.List[len(comma.List)-1]
 458	}
 459	if binary, ok := i.(*js.BinaryExpr); ok && binary.Op == js.EqToken {
 460		i = binary.X // return first
 461	}
 462	return i
 463}
 464
 465func isTrue(i js.IExpr) bool {
 466	i = innerExpr(i)
 467	if lit, ok := i.(*js.LiteralExpr); ok && lit.TokenType == js.TrueToken {
 468		return true
 469	} else if unary, ok := i.(*js.UnaryExpr); ok && unary.Op == js.NotToken {
 470		ret, _ := isFalsy(unary.X)
 471		return ret
 472	}
 473	return false
 474}
 475
 476func isFalse(i js.IExpr) bool {
 477	i = innerExpr(i)
 478	if lit, ok := i.(*js.LiteralExpr); ok {
 479		return lit.TokenType == js.FalseToken
 480	} else if unary, ok := i.(*js.UnaryExpr); ok && unary.Op == js.NotToken {
 481		ret, _ := isTruthy(unary.X)
 482		return ret
 483	}
 484	return false
 485}
 486
 487func isEqualExpr(a, b js.IExpr) bool {
 488	a = innerExpr(a)
 489	b = innerExpr(b)
 490	if left, ok := a.(*js.Var); ok {
 491		if right, ok := b.(*js.Var); ok {
 492			return bytes.Equal(left.Name(), right.Name())
 493		}
 494	}
 495	// TODO: use reflect.DeepEqual?
 496	return false
 497}
 498
 499func toNullishExpr(condExpr *js.CondExpr) (js.IExpr, bool) {
 500	if v, not, ok := isUndefinedOrNullVar(condExpr.Cond); ok {
 501		left, right := condExpr.X, condExpr.Y
 502		if not {
 503			left, right = right, left
 504		}
 505		if isEqualExpr(v, right) {
 506			// convert conditional expression to nullish:  a==null?b:a  =>  a??b
 507			return &js.BinaryExpr{js.NullishToken, groupExpr(right, binaryLeftPrecMap[js.NullishToken]), groupExpr(left, binaryRightPrecMap[js.NullishToken])}, true
 508		} else if isUndefined(left) {
 509			// convert conditional expression to optional expr:  a==null?undefined:a.b  =>  a?.b
 510			expr := right
 511			var parent js.IExpr
 512			for {
 513				prevExpr := expr
 514				if callExpr, ok := expr.(*js.CallExpr); ok {
 515					expr = callExpr.X
 516				} else if dotExpr, ok := expr.(*js.DotExpr); ok {
 517					expr = dotExpr.X
 518				} else if indexExpr, ok := expr.(*js.IndexExpr); ok {
 519					expr = indexExpr.X
 520				} else if templateExpr, ok := expr.(*js.TemplateExpr); ok {
 521					expr = templateExpr.Tag
 522				} else {
 523					break
 524				}
 525				parent = prevExpr
 526			}
 527			if parent != nil && isEqualExpr(v, expr) {
 528				if callExpr, ok := parent.(*js.CallExpr); ok {
 529					callExpr.Optional = true
 530				} else if dotExpr, ok := parent.(*js.DotExpr); ok {
 531					dotExpr.Optional = true
 532				} else if indexExpr, ok := parent.(*js.IndexExpr); ok {
 533					indexExpr.Optional = true
 534				} else if templateExpr, ok := parent.(*js.TemplateExpr); ok {
 535					templateExpr.Optional = true
 536				}
 537				return right, true
 538			}
 539		}
 540	}
 541	return nil, false
 542}
 543
 544func isUndefinedOrNullVar(i js.IExpr) (*js.Var, bool, bool) {
 545	i = innerExpr(i)
 546	if binary, ok := i.(*js.BinaryExpr); ok && (binary.Op == js.OrToken || binary.Op == js.AndToken) {
 547		eqEqOp := js.EqEqToken
 548		eqEqEqOp := js.EqEqEqToken
 549		if binary.Op == js.AndToken {
 550			eqEqOp = js.NotEqToken
 551			eqEqEqOp = js.NotEqEqToken
 552		}
 553
 554		left, isBinaryX := innerExpr(binary.X).(*js.BinaryExpr)
 555		right, isBinaryY := innerExpr(binary.Y).(*js.BinaryExpr)
 556		if isBinaryX && isBinaryY && (left.Op == eqEqOp || left.Op == eqEqEqOp) && (right.Op == eqEqOp || right.Op == eqEqEqOp) {
 557			var leftVar, rightVar *js.Var
 558			if v, ok := left.X.(*js.Var); ok && isUndefinedOrNull(left.Y) {
 559				leftVar = v
 560			} else if v, ok := left.Y.(*js.Var); ok && isUndefinedOrNull(left.X) {
 561				leftVar = v
 562			}
 563			if v, ok := right.X.(*js.Var); ok && isUndefinedOrNull(right.Y) {
 564				rightVar = v
 565			} else if v, ok := right.Y.(*js.Var); ok && isUndefinedOrNull(right.X) {
 566				rightVar = v
 567			}
 568			if leftVar != nil && leftVar == rightVar {
 569				return leftVar, binary.Op == js.AndToken, true
 570			}
 571		}
 572	} else if ok && (binary.Op == js.EqEqToken || binary.Op == js.NotEqToken) {
 573		var variable *js.Var
 574		if v, ok := binary.X.(*js.Var); ok && isUndefinedOrNull(binary.Y) {
 575			variable = v
 576		} else if v, ok := binary.Y.(*js.Var); ok && isUndefinedOrNull(binary.X) {
 577			variable = v
 578		}
 579		if variable != nil {
 580			return variable, binary.Op == js.NotEqToken, true
 581		}
 582	}
 583	return nil, false, false
 584}
 585
 586func isUndefinedOrNull(i js.IExpr) bool {
 587	i = innerExpr(i)
 588	if lit, ok := i.(*js.LiteralExpr); ok {
 589		return lit.TokenType == js.NullToken
 590	}
 591	return isUndefined(i)
 592}
 593
 594func isUndefined(i js.IExpr) bool {
 595	i = innerExpr(i)
 596	if v, ok := i.(*js.Var); ok {
 597		if bytes.Equal(v.Name(), undefinedBytes) { // TODO: only if not defined
 598			return true
 599		}
 600	} else if unary, ok := i.(*js.UnaryExpr); ok && unary.Op == js.VoidToken {
 601		return !hasSideEffects(unary.X)
 602	}
 603	return false
 604}
 605
 606// returns whether truthy and whether it could be coerced to a boolean (i.e. when returns (false,true) this means it is falsy)
 607func isTruthy(i js.IExpr) (bool, bool) {
 608	if falsy, ok := isFalsy(i); ok {
 609		return !falsy, true
 610	}
 611	return false, false
 612}
 613
 614// returns whether falsy and whether it could be coerced to a boolean (i.e. when returns (false,true) this means it is truthy)
 615func isFalsy(i js.IExpr) (bool, bool) {
 616	negated := false
 617	group, isGroup := i.(*js.GroupExpr)
 618	unary, isUnary := i.(*js.UnaryExpr)
 619	for isGroup || isUnary && unary.Op == js.NotToken {
 620		if isGroup {
 621			i = group.X
 622		} else {
 623			i = unary.X
 624			negated = !negated
 625		}
 626		group, isGroup = i.(*js.GroupExpr)
 627		unary, isUnary = i.(*js.UnaryExpr)
 628	}
 629	if lit, ok := i.(*js.LiteralExpr); ok {
 630		tt := lit.TokenType
 631		d := lit.Data
 632		if tt == js.FalseToken || tt == js.NullToken || tt == js.StringToken && len(lit.Data) == 0 {
 633			return !negated, true // falsy
 634		} else if tt == js.TrueToken || tt == js.StringToken {
 635			return negated, true // truthy
 636		} else if tt == js.DecimalToken || tt == js.BinaryToken || tt == js.OctalToken || tt == js.HexadecimalToken || tt == js.BigIntToken {
 637			for _, c := range d {
 638				if c == 'e' || c == 'E' || c == 'n' {
 639					break
 640				} else if c != '0' && c != '.' && c != 'x' && c != 'X' && c != 'b' && c != 'B' && c != 'o' && c != 'O' {
 641					return negated, true // truthy
 642				}
 643			}
 644			return !negated, true // falsy
 645		}
 646	} else if isUndefined(i) {
 647		return !negated, true // falsy
 648	} else if v, ok := i.(*js.Var); ok && bytes.Equal(v.Name(), nanBytes) {
 649		return !negated, true // falsy
 650	}
 651	return false, false // unknown
 652}
 653
 654func isBooleanExpr(expr js.IExpr) bool {
 655	if unaryExpr, ok := expr.(*js.UnaryExpr); ok {
 656		return unaryExpr.Op == js.NotToken
 657	} else if binaryExpr, ok := expr.(*js.BinaryExpr); ok {
 658		op := binaryOpPrecMap[binaryExpr.Op]
 659		if op == js.OpAnd || op == js.OpOr {
 660			return isBooleanExpr(binaryExpr.X) && isBooleanExpr(binaryExpr.Y)
 661		}
 662		return op == js.OpCompare || op == js.OpEquals
 663	} else if litExpr, ok := expr.(*js.LiteralExpr); ok {
 664		return litExpr.TokenType == js.TrueToken || litExpr.TokenType == js.FalseToken
 665	} else if groupExpr, ok := expr.(*js.GroupExpr); ok {
 666		return isBooleanExpr(groupExpr.X)
 667	}
 668	return false
 669}
 670
 671func invertBooleanOp(op js.TokenType) js.TokenType {
 672	if op == js.EqEqToken {
 673		return js.NotEqToken
 674	} else if op == js.NotEqToken {
 675		return js.EqEqToken
 676	} else if op == js.EqEqEqToken {
 677		return js.NotEqEqToken
 678	} else if op == js.NotEqEqToken {
 679		return js.EqEqEqToken
 680	}
 681	return js.ErrorToken
 682}
 683
 684func optimizeBooleanExpr(expr js.IExpr, invert bool, prec js.OpPrec) js.IExpr {
 685	if invert {
 686		// unary !(boolean) has already been handled
 687		if binaryExpr, ok := expr.(*js.BinaryExpr); ok && binaryOpPrecMap[binaryExpr.Op] == js.OpEquals {
 688			binaryExpr.Op = invertBooleanOp(binaryExpr.Op)
 689			return expr
 690		} else {
 691			return optimizeUnaryExpr(&js.UnaryExpr{js.NotToken, groupExpr(expr, js.OpUnary)}, prec)
 692		}
 693	} else if isBooleanExpr(expr) {
 694		return groupExpr(expr, prec)
 695	} else {
 696		return &js.UnaryExpr{js.NotToken, &js.UnaryExpr{js.NotToken, groupExpr(expr, js.OpUnary)}}
 697	}
 698}
 699
 700func optimizeUnaryExpr(expr *js.UnaryExpr, prec js.OpPrec) js.IExpr {
 701	if expr.Op == js.NotToken {
 702		invert := true
 703		var expr2 js.IExpr = expr.X
 704		for {
 705			if unary, ok := expr2.(*js.UnaryExpr); ok && unary.Op == js.NotToken {
 706				invert = !invert
 707				expr2 = unary.X
 708			} else if group, ok := expr2.(*js.GroupExpr); ok {
 709				expr2 = group.X
 710			} else {
 711				break
 712			}
 713		}
 714		if !invert && isBooleanExpr(expr2) {
 715			return groupExpr(expr2, prec)
 716		} else if binary, ok := expr2.(*js.BinaryExpr); ok && invert {
 717			if binaryOpPrecMap[binary.Op] == js.OpEquals {
 718				binary.Op = invertBooleanOp(binary.Op)
 719				return groupExpr(binary, prec)
 720			} else if binary.Op == js.AndToken || binary.Op == js.OrToken {
 721				op := js.AndToken
 722				if binary.Op == js.AndToken {
 723					op = js.OrToken
 724				}
 725				precInside := binaryOpPrecMap[op]
 726				needsGroup := precInside < prec && (precInside != js.OpCoalesce || prec != js.OpBitOr)
 727
 728				// rewrite !(a||b) to !a&&!b
 729				// rewrite !(a==0||b==0) to a!=0&&b!=0
 730				score := 3 // savings if rewritten (group parentheses and not-token)
 731				if needsGroup {
 732					score -= 2
 733				}
 734				score -= 2 // add two not-tokens for left and right
 735
 736				// == and === can become != and !==
 737				var isEqX, isEqY bool
 738				if binaryExpr, ok := binary.X.(*js.BinaryExpr); ok && binaryOpPrecMap[binaryExpr.Op] == js.OpEquals {
 739					score += 1
 740					isEqX = true
 741				}
 742				if binaryExpr, ok := binary.Y.(*js.BinaryExpr); ok && binaryOpPrecMap[binaryExpr.Op] == js.OpEquals {
 743					score += 1
 744					isEqY = true
 745				}
 746
 747				// add group if it wasn't already there
 748				var needsGroupX, needsGroupY bool
 749				if !isEqX && binaryLeftPrecMap[binary.Op] <= exprPrec(binary.X) && exprPrec(binary.X) < js.OpUnary {
 750					score -= 2
 751					needsGroupX = true
 752				}
 753				if !isEqY && binaryRightPrecMap[binary.Op] <= exprPrec(binary.Y) && exprPrec(binary.Y) < js.OpUnary {
 754					score -= 2
 755					needsGroupY = true
 756				}
 757
 758				// remove group
 759				if op == js.OrToken {
 760					if exprPrec(binary.X) == js.OpOr {
 761						score += 2
 762					}
 763					if exprPrec(binary.Y) == js.OpAnd {
 764						score += 2
 765					}
 766				}
 767
 768				if 0 < score {
 769					binary.Op = op
 770					if isEqX {
 771						binary.X.(*js.BinaryExpr).Op = invertBooleanOp(binary.X.(*js.BinaryExpr).Op)
 772					}
 773					if isEqY {
 774						binary.Y.(*js.BinaryExpr).Op = invertBooleanOp(binary.Y.(*js.BinaryExpr).Op)
 775					}
 776					if needsGroupX {
 777						binary.X = &js.GroupExpr{binary.X}
 778					}
 779					if needsGroupY {
 780						binary.Y = &js.GroupExpr{binary.Y}
 781					}
 782					if !isEqX {
 783						binary.X = &js.UnaryExpr{js.NotToken, binary.X}
 784					}
 785					if !isEqY {
 786						binary.Y = &js.UnaryExpr{js.NotToken, binary.Y}
 787					}
 788					if needsGroup {
 789						return &js.GroupExpr{binary}
 790					}
 791					return binary
 792				}
 793			}
 794		}
 795	}
 796	return expr
 797}
 798
 799func (m *jsMinifier) optimizeCondExpr(expr *js.CondExpr, prec js.OpPrec) js.IExpr {
 800	// remove double negative !! in condition, or switch cases for single negative !
 801	if unary1, ok := expr.Cond.(*js.UnaryExpr); ok && unary1.Op == js.NotToken {
 802		if unary2, ok := unary1.X.(*js.UnaryExpr); ok && unary2.Op == js.NotToken {
 803			if isBooleanExpr(unary2.X) {
 804				expr.Cond = unary2.X
 805			}
 806		} else {
 807			expr.Cond = unary1.X
 808			expr.X, expr.Y = expr.Y, expr.X
 809		}
 810	}
 811
 812	finalCond := finalExpr(expr.Cond)
 813	if truthy, ok := isTruthy(expr.Cond); truthy && ok {
 814		// if condition is truthy
 815		return expr.X
 816	} else if !truthy && ok {
 817		// if condition is falsy
 818		return expr.Y
 819	} else if isEqualExpr(finalCond, expr.X) && (exprPrec(finalCond) < js.OpAssign || binaryLeftPrecMap[js.OrToken] <= exprPrec(finalCond)) && (exprPrec(expr.Y) < js.OpAssign || binaryRightPrecMap[js.OrToken] <= exprPrec(expr.Y)) {
 820		// if condition is equal to true body
 821		// for higher prec we need to add group parenthesis, and for lower prec we have parenthesis anyways. This only is shorter if len(expr.X) >= 3. isEqualExpr only checks for literal variables, which is a name will be minified to a one or two character name.
 822		return &js.BinaryExpr{js.OrToken, groupExpr(expr.Cond, binaryLeftPrecMap[js.OrToken]), expr.Y}
 823	} else if isEqualExpr(finalCond, expr.Y) && (exprPrec(finalCond) < js.OpAssign || binaryLeftPrecMap[js.AndToken] <= exprPrec(finalCond)) && (exprPrec(expr.X) < js.OpAssign || binaryRightPrecMap[js.AndToken] <= exprPrec(expr.X)) {
 824		// if condition is equal to false body
 825		// for higher prec we need to add group parenthesis, and for lower prec we have parenthesis anyways. This only is shorter if len(expr.X) >= 3. isEqualExpr only checks for literal variables, which is a name will be minified to a one or two character name.
 826		return &js.BinaryExpr{js.AndToken, groupExpr(expr.Cond, binaryLeftPrecMap[js.AndToken]), expr.X}
 827	} else if isEqualExpr(expr.X, expr.Y) {
 828		// if true and false bodies are equal
 829		return groupExpr(&js.CommaExpr{[]js.IExpr{expr.Cond, expr.X}}, prec)
 830	} else if nullishExpr, ok := toNullishExpr(expr); ok && m.o.minVersion(2020) {
 831		// no need to check whether left/right need to add groups, as the space saving is always more
 832		return nullishExpr
 833	} else {
 834		callX, isCallX := expr.X.(*js.CallExpr)
 835		callY, isCallY := expr.Y.(*js.CallExpr)
 836		if isCallX && isCallY && len(callX.Args.List) == 1 && len(callY.Args.List) == 1 && !callX.Args.List[0].Rest && !callY.Args.List[0].Rest && isEqualExpr(callX.X, callY.X) {
 837			expr.X = callX.Args.List[0].Value
 838			expr.Y = callY.Args.List[0].Value
 839			return &js.CallExpr{callX.X, js.Args{[]js.Arg{{expr, false}}}, false} // recompress the conditional expression inside
 840		}
 841
 842		// shorten when true and false bodies are true and false
 843		trueX, falseX := isTrue(expr.X), isFalse(expr.X)
 844		trueY, falseY := isTrue(expr.Y), isFalse(expr.Y)
 845		if trueX && falseY || falseX && trueY {
 846			return optimizeBooleanExpr(expr.Cond, falseX, prec)
 847		} else if trueX || trueY {
 848			// trueX != trueY
 849			cond := optimizeBooleanExpr(expr.Cond, trueY, binaryLeftPrecMap[js.OrToken])
 850			if trueY {
 851				return &js.BinaryExpr{js.OrToken, cond, groupExpr(expr.X, binaryRightPrecMap[js.OrToken])}
 852			} else {
 853				return &js.BinaryExpr{js.OrToken, cond, groupExpr(expr.Y, binaryRightPrecMap[js.OrToken])}
 854			}
 855		} else if falseX || falseY {
 856			// falseX != falseY
 857			cond := optimizeBooleanExpr(expr.Cond, falseX, binaryLeftPrecMap[js.AndToken])
 858			if falseX {
 859				return &js.BinaryExpr{js.AndToken, cond, groupExpr(expr.Y, binaryRightPrecMap[js.AndToken])}
 860			} else {
 861				return &js.BinaryExpr{js.AndToken, cond, groupExpr(expr.X, binaryRightPrecMap[js.AndToken])}
 862			}
 863		} else if condExpr, ok := expr.X.(*js.CondExpr); ok && isEqualExpr(expr.Y, condExpr.Y) {
 864			// nested conditional expression with same false bodies
 865			return &js.CondExpr{&js.BinaryExpr{js.AndToken, groupExpr(expr.Cond, binaryLeftPrecMap[js.AndToken]), groupExpr(condExpr.Cond, binaryRightPrecMap[js.AndToken])}, condExpr.X, expr.Y}
 866		} else if prec <= js.OpExpr {
 867			// regular conditional expression
 868			// convert  (a,b)?c:d  =>  a,b?c:d
 869			if group, ok := expr.Cond.(*js.GroupExpr); ok {
 870				if comma, ok := group.X.(*js.CommaExpr); ok && js.OpCoalesce <= exprPrec(comma.List[len(comma.List)-1]) {
 871					expr.Cond = comma.List[len(comma.List)-1]
 872					comma.List[len(comma.List)-1] = expr
 873					return comma // recompress the conditional expression inside
 874				}
 875			}
 876		}
 877	}
 878	return expr
 879}
 880
 881func isHexDigit(b byte) bool {
 882	return '0' <= b && b <= '9' || 'a' <= b && b <= 'f' || 'A' <= b && b <= 'F'
 883}
 884
 885func mergeBinaryExpr(expr *js.BinaryExpr) {
 886	// merge string concatenations which may be intertwined with other additions
 887	var ok bool
 888	for expr.Op == js.AddToken {
 889		if lit, ok := expr.Y.(*js.LiteralExpr); ok && lit.TokenType == js.StringToken {
 890			left := expr
 891			strings := []*js.LiteralExpr{lit}
 892			n := len(lit.Data) - 2
 893			for left.Op == js.AddToken {
 894				if 50 < len(strings) {
 895					return // limit recursion
 896				}
 897				if lit, ok := left.X.(*js.LiteralExpr); ok && lit.TokenType == js.StringToken {
 898					strings = append(strings, lit)
 899					n += len(lit.Data) - 2
 900					left.X = nil
 901				} else if newLeft, ok := left.X.(*js.BinaryExpr); ok {
 902					if lit, ok := newLeft.Y.(*js.LiteralExpr); ok && lit.TokenType == js.StringToken {
 903						strings = append(strings, lit)
 904						n += len(lit.Data) - 2
 905						left = newLeft
 906						continue
 907					}
 908				}
 909				break
 910			}
 911
 912			if 1 < len(strings) {
 913				// unescaped quotes will be repaired in minifyString later on
 914				b := make([]byte, 0, n+2)
 915				b = append(b, strings[len(strings)-1].Data[:len(strings[len(strings)-1].Data)-1]...)
 916				for i := len(strings) - 2; 0 < i; i-- {
 917					b = append(b, strings[i].Data[1:len(strings[i].Data)-1]...)
 918				}
 919				b = append(b, strings[0].Data[1:]...)
 920				b[len(b)-1] = b[0]
 921
 922				expr.X = left.X
 923				expr.Y.(*js.LiteralExpr).Data = b
 924			}
 925		}
 926		if expr, ok = expr.X.(*js.BinaryExpr); !ok {
 927			break
 928		}
 929	}
 930}
 931
 932func minifyString(b []byte, allowTemplate bool) []byte {
 933	if len(b) < 3 {
 934		return []byte("\"\"")
 935	}
 936
 937	// switch quotes if more optimal
 938	singleQuotes := 0
 939	doubleQuotes := 0
 940	backtickQuotes := 0
 941	newlines := 0
 942	dollarSigns := 0
 943	notEscapes := false
 944	for i := 1; i < len(b)-1; i++ {
 945		if b[i] == '\'' {
 946			singleQuotes++
 947		} else if b[i] == '"' {
 948			doubleQuotes++
 949		} else if b[i] == '`' {
 950			backtickQuotes++
 951		} else if b[i] == '$' {
 952			dollarSigns++
 953		} else if b[i] == '\\' && i+1 < len(b) {
 954			if b[i+1] == 'n' || b[i+1] == 'r' {
 955				newlines++
 956			} else if '1' <= b[i+1] && b[i+1] <= '9' || b[i+1] == '0' && i+2 < len(b) && '0' <= b[i+2] && b[i+2] <= '9' {
 957				notEscapes = true
 958			}
 959		}
 960	}
 961	quote := byte('"') // default to " for better GZIP compression
 962	quotes := singleQuotes
 963	if doubleQuotes < singleQuotes {
 964		quote = byte('"')
 965		quotes = doubleQuotes
 966	} else if singleQuotes < doubleQuotes {
 967		quote = byte('\'')
 968	}
 969	if allowTemplate && !notEscapes && backtickQuotes+dollarSigns < quotes+newlines {
 970		quote = byte('`')
 971	}
 972	b[0] = quote
 973	b[len(b)-1] = quote
 974
 975	// strip unnecessary escapes
 976	return replaceEscapes(b, quote, 1, 1)
 977}
 978
 979func replaceEscapes(b []byte, quote byte, prefix, suffix int) []byte {
 980	// strip unnecessary escapes
 981	j := 0
 982	start := 0
 983	for i := prefix; i < len(b)-suffix; i++ {
 984		if c := b[i]; c == '\\' {
 985			c = b[i+1]
 986			if c == quote || c == '\\' || quote != '`' && (c == 'n' || c == 'r') || c == '0' && (i+2 == len(b)-1 || b[i+2] < '0' || '7' < b[i+2]) {
 987				// keep escape sequence
 988				i++
 989				continue
 990			}
 991			n := 1 // number of characters to skip
 992			if c == '\n' || c == '\r' || c == 0xE2 && i+3 < len(b)-1 && b[i+2] == 0x80 && (b[i+3] == 0xA8 || b[i+3] == 0xA9) {
 993				// line continuations
 994				if c == 0xE2 {
 995					n = 4
 996				} else if c == '\r' && i+2 < len(b)-1 && b[i+2] == '\n' {
 997					n = 3
 998				} else {
 999					n = 2
1000				}
1001			} else if c == 'x' {
1002				if i+3 < len(b)-1 && isHexDigit(b[i+2]) && b[i+2] < '8' && isHexDigit(b[i+3]) && (!(b[i+2] == '0' && b[i+3] == '0') || i+3 == len(b) || b[i+3] != '\\' && (b[i+3] < '0' && '7' < b[i+3])) {
1003					// don't convert \x00 to \0 if it may be an octal number
1004					// hexadecimal escapes
1005					_, _ = hex.Decode(b[i+3:i+4:i+4], b[i+2:i+4])
1006					n = 3
1007					if b[i+3] == '\\' || b[i+3] == quote || b[i+3] == '\n' || b[i+3] == '\r' || b[i+3] == 0 {
1008						if b[i+3] == '\n' {
1009							b[i+3] = 'n'
1010						} else if b[i+3] == '\r' {
1011							b[i+3] = 'r'
1012						}
1013						n--
1014						b[i+2] = '\\'
1015					}
1016				} else {
1017					i++
1018					continue
1019				}
1020			} else if c == 'u' && i+2 < len(b) {
1021				l := i + 2
1022				if b[i+2] == '{' {
1023					l++
1024				}
1025				r := l
1026				for ; r < len(b) && (b[i+2] == '{' || r < l+4); r++ {
1027					if b[r] < '0' || '9' < b[r] && b[r] < 'A' || 'F' < b[r] && b[r] < 'a' || 'f' < b[r] {
1028						break
1029					}
1030				}
1031				if b[i+2] == '{' && 6 < r-l || b[i+2] != '{' && r-l != 4 {
1032					i++
1033					continue
1034				}
1035				num, err := stdStrconv.ParseInt(string(b[l:r]), 16, 32)
1036				if err != nil || 0x10FFFF <= num {
1037					i++
1038					continue
1039				}
1040
1041				if num == 0 {
1042					// don't convert NULL to literal NULL (gives JS parsing problems)
1043					if r == len(b) || b[r] != '\\' && (b[r] < '0' && '7' < b[r]) {
1044						b[r-2] = '\\'
1045						n = r - l
1046					} else {
1047						// don't convert NULL to \0 (may be an octal number)
1048						b[r-4] = '\\'
1049						b[r-3] = 'x'
1050						n = r - l - 2
1051					}
1052				} else {
1053					// decode unicode character to UTF-8 and put at the end of the escape sequence
1054					// then skip the first part of the escape sequence until the decoded character
1055					n = 2 + r - l
1056					if b[i+2] == '{' {
1057						n += 2
1058					}
1059					m := utf8.RuneLen(rune(num))
1060					if m == -1 {
1061						i++
1062						continue
1063					}
1064					utf8.EncodeRune(b[i+n-m:], rune(num))
1065					n -= m
1066				}
1067			} else if '0' <= c && c <= '7' {
1068				// octal escapes (legacy), \0 already handled
1069				num := c - '0'
1070				if i+2 < len(b)-1 && '0' <= b[i+2] && b[i+2] <= '7' {
1071					num = num*8 + b[i+2] - '0'
1072					n++
1073					if num < 32 && i+3 < len(b)-1 && '0' <= b[i+3] && b[i+3] <= '7' {
1074						num = num*8 + b[i+3] - '0'
1075						n++
1076					}
1077				}
1078				b[i+n] = num
1079				if num == 0 || num == '\\' || num == quote || num == '\n' || num == '\r' {
1080					if num == 0 {
1081						b[i+n] = '0'
1082					} else if num == '\n' {
1083						b[i+n] = 'n'
1084					} else if num == '\r' {
1085						b[i+n] = 'r'
1086					}
1087					n--
1088					b[i+n] = '\\'
1089				}
1090			} else if c == 'n' {
1091				b[i+1] = '\n' // only for template literals
1092			} else if c == 'r' {
1093				b[i+1] = '\r' // only for template literals
1094			} else if c == 't' {
1095				b[i+1] = '\t'
1096			} else if c == 'f' {
1097				b[i+1] = '\f'
1098			} else if c == 'v' {
1099				b[i+1] = '\v'
1100			} else if c == 'b' {
1101				b[i+1] = '\b'
1102			}
1103			// remove unnecessary escape character, anything but 0x00, 0x0A, 0x0D, \, ' or "
1104			if start != 0 {
1105				j += copy(b[j:], b[start:i])
1106			} else {
1107				j = i
1108			}
1109			start = i + n
1110			i += n - 1
1111		} else if c == quote || c == '$' && quote == '`' && (i+1 < len(b) && b[i+1] == '{' || i+2 < len(b) && b[i+1] == '\\' && b[i+2] == '{') {
1112			// may not be escaped properly when changing quotes
1113			if j < start {
1114				// avoid append
1115				j += copy(b[j:], b[start:i])
1116				b[j] = '\\'
1117				j++
1118				start = i
1119			} else {
1120				b = append(append(b[:i], '\\'), b[i:]...)
1121				i++
1122				b[i] = c // was overwritten above
1123			}
1124		} else if c == '<' && 9 <= len(b)-1-i {
1125			if b[i+1] == '\\' && 10 <= len(b)-1-i && bytes.Equal(b[i+2:i+10], []byte("/script>")) {
1126				i += 9
1127			} else if bytes.Equal(b[i+1:i+9], []byte("/script>")) {
1128				i++
1129				if j < start {
1130					// avoid append
1131					j += copy(b[j:], b[start:i])
1132					b[j] = '\\'
1133					j++
1134					start = i
1135				} else {
1136					b = append(append(b[:i], '\\'), b[i:]...)
1137					i++
1138					b[i] = '/' // was overwritten above
1139				}
1140			}
1141		}
1142	}
1143	if start != 0 {
1144		j += copy(b[j:], b[start:])
1145		return b[:j]
1146	}
1147	return b
1148}
1149
1150var regexpEscapeTable = [256]bool{
1151	// ASCII
1152	false, false, false, false, false, false, false, false,
1153	false, false, false, false, false, false, false, false,
1154	false, false, false, false, false, false, false, false,
1155	false, false, false, false, false, false, false, false,
1156
1157	false, false, false, false, true, false, false, false, // $
1158	true, true, true, true, false, false, true, true, // (, ), *, +, ., /
1159	true, true, true, true, true, true, true, true, // 0, 1, 2, 3, 4, 5, 6, 7
1160	true, true, false, false, false, false, false, true, // 8, 9, ?
1161
1162	false, false, true, false, true, false, false, false, // B, D
1163	false, false, false, false, false, false, false, false,
1164	true, false, false, true, false, false, false, true, // P, S, W
1165	false, false, false, true, true, true, true, false, // [, \, ], ^
1166
1167	false, false, true, true, true, false, true, false, // b, c, d, f
1168	false, false, false, true, false, false, true, false, // k, n
1169	true, false, true, true, true, true, true, true, // p, r, s, t, u, v, w
1170	true, false, false, true, true, true, false, false, // x, {, |, }
1171
1172	// non-ASCII
1173	false, false, false, false, false, false, false, false,
1174	false, false, false, false, false, false, false, false,
1175	false, false, false, false, false, false, false, false,
1176	false, false, false, false, false, false, false, false,
1177
1178	false, false, false, false, false, false, false, false,
1179	false, false, false, false, false, false, false, false,
1180	false, false, false, false, false, false, false, false,
1181	false, false, false, false, false, false, false, false,
1182
1183	false, false, false, false, false, false, false, false,
1184	false, false, false, false, false, false, false, false,
1185	false, false, false, false, false, false, false, false,
1186	false, false, false, false, false, false, false, false,
1187
1188	false, false, false, false, false, false, false, false,
1189	false, false, false, false, false, false, false, false,
1190	false, false, false, false, false, false, false, false,
1191	false, false, false, false, false, false, false, false,
1192}
1193
1194var regexpClassEscapeTable = [256]bool{
1195	// ASCII
1196	false, false, false, false, false, false, false, false,
1197	false, false, false, false, false, false, false, false,
1198	false, false, false, false, false, false, false, false,
1199	false, false, false, false, false, false, false, false,
1200
1201	false, false, false, false, false, false, false, false,
1202	false, false, false, false, false, false, false, false,
1203	true, true, true, true, true, true, true, true, // 0, 1, 2, 3, 4, 5, 6, 7
1204	true, true, false, false, false, false, false, false, // 8, 9
1205
1206	false, false, false, false, true, false, false, false, // D
1207	false, false, false, false, false, false, false, false,
1208	true, false, false, true, false, false, false, true, // P, S, W
1209	false, false, false, false, true, true, false, false, // \, ]
1210
1211	false, false, true, true, true, false, true, false, // b, c, d, f
1212	false, false, false, false, false, false, true, false, // n
1213	true, false, true, true, true, true, true, true, // p, r, s, t, u, v, w
1214	true, false, false, false, false, false, false, false, // x
1215
1216	// non-ASCII
1217	false, false, false, false, false, false, false, false,
1218	false, false, false, false, false, false, false, false,
1219	false, false, false, false, false, false, false, false,
1220	false, false, false, false, false, false, false, false,
1221
1222	false, false, false, false, false, false, false, false,
1223	false, false, false, false, false, false, false, false,
1224	false, false, false, false, false, false, false, false,
1225	false, false, false, false, false, false, false, false,
1226
1227	false, false, false, false, false, false, false, false,
1228	false, false, false, false, false, false, false, false,
1229	false, false, false, false, false, false, false, false,
1230	false, false, false, false, false, false, false, false,
1231
1232	false, false, false, false, false, false, false, false,
1233	false, false, false, false, false, false, false, false,
1234	false, false, false, false, false, false, false, false,
1235	false, false, false, false, false, false, false, false,
1236}
1237
1238func minifyRegExp(b []byte) []byte {
1239	inClass := false
1240	afterDash := 0
1241	iClass := 0
1242	for i := 1; i < len(b)-1; i++ {
1243		if inClass {
1244			afterDash++
1245		}
1246		if b[i] == '\\' {
1247			c := b[i+1]
1248			escape := true
1249			if inClass {
1250				escape = regexpClassEscapeTable[c] || c == '-' && 2 < afterDash && i+2 < len(b) && b[i+2] != ']' || c == '^' && i == iClass+1
1251			} else {
1252				escape = regexpEscapeTable[c]
1253			}
1254			if !escape {
1255				b = append(b[:i], b[i+1:]...)
1256				if inClass && 2 < afterDash && c == '-' {
1257					afterDash = 0
1258				} else if inClass && c == '^' {
1259					afterDash = 1
1260				}
1261			} else {
1262				i++
1263			}
1264		} else if b[i] == '[' {
1265			if b[i+1] == '^' {
1266				i++
1267			}
1268			afterDash = 1
1269			inClass = true
1270			iClass = i
1271		} else if inClass && b[i] == ']' {
1272			inClass = false
1273		} else if b[i] == '/' {
1274			break
1275		} else if inClass && 2 < afterDash && b[i] == '-' {
1276			afterDash = 0
1277		}
1278	}
1279	return b
1280}
1281
1282func removeUnderscores(b []byte) []byte {
1283	for i := 0; i < len(b); i++ {
1284		if b[i] == '_' {
1285			b = append(b[:i], b[i+1:]...)
1286			i--
1287		}
1288	}
1289	return b
1290}
1291
1292func decimalNumber(b []byte, prec int) []byte {
1293	b = removeUnderscores(b)
1294	return minify.Number(b, prec)
1295}
1296
1297func binaryNumber(b []byte, prec int) []byte {
1298	b = removeUnderscores(b)
1299	if len(b) <= 2 || 65 < len(b) {
1300		return b
1301	}
1302	var n int64
1303	for _, c := range b[2:] {
1304		n *= 2
1305		n += int64(c - '0')
1306	}
1307	i := strconv.LenInt(n) - 1
1308	b = b[:i+1]
1309	for 0 <= i {
1310		b[i] = byte('0' + n%10)
1311		n /= 10
1312		i--
1313	}
1314	return minify.Number(b, prec)
1315}
1316
1317func octalNumber(b []byte, prec int) []byte {
1318	b = removeUnderscores(b)
1319	if len(b) <= 2 || 23 < len(b) {
1320		return b
1321	}
1322	var n int64
1323	for _, c := range b[2:] {
1324		n *= 8
1325		n += int64(c - '0')
1326	}
1327	i := strconv.LenInt(n) - 1
1328	b = b[:i+1]
1329	for 0 <= i {
1330		b[i] = byte('0' + n%10)
1331		n /= 10
1332		i--
1333	}
1334	return minify.Number(b, prec)
1335}
1336
1337func hexadecimalNumber(b []byte, prec int) []byte {
1338	b = removeUnderscores(b)
1339	if len(b) <= 2 || 12 < len(b) || len(b) == 12 && ('D' < b[2] && b[2] <= 'F' || 'd' < b[2]) {
1340		return b
1341	}
1342	var n int64
1343	for _, c := range b[2:] {
1344		n *= 16
1345		if c <= '9' {
1346			n += int64(c - '0')
1347		} else if c <= 'F' {
1348			n += 10 + int64(c-'A')
1349		} else {
1350			n += 10 + int64(c-'a')
1351		}
1352	}
1353	i := strconv.LenInt(n) - 1
1354	b = b[:i+1]
1355	for 0 <= i {
1356		b[i] = byte('0' + n%10)
1357		n /= 10
1358		i--
1359	}
1360	return minify.Number(b, prec)
1361}