1// Package js minifies ECMAScript 2021 following the language specification at https://tc39.es/ecma262/.
   2package js
   3
   4import (
   5	"bytes"
   6	"io"
   7
   8	"github.com/tdewolff/minify/v2"
   9	"github.com/tdewolff/parse/v2"
  10	"github.com/tdewolff/parse/v2/js"
  11)
  12
  13type blockType int
  14
  15const (
  16	defaultBlock blockType = iota
  17	functionBlock
  18	iterationBlock
  19)
  20
  21// Minifier is a JS minifier.
  22type Minifier struct {
  23	Precision           int // number of significant digits
  24	KeepVarNames        bool
  25	useAlphabetVarNames bool
  26	Version             int
  27}
  28
  29func (o *Minifier) minVersion(version int) bool {
  30	return o.Version == 0 || version <= o.Version
  31}
  32
  33// Minify minifies JS data, it reads from r and writes to w.
  34func Minify(m *minify.M, w io.Writer, r io.Reader, params map[string]string) error {
  35	return (&Minifier{}).Minify(m, w, r, params)
  36}
  37
  38// Minify minifies JS data, it reads from r and writes to w.
  39func (o *Minifier) Minify(_ *minify.M, w io.Writer, r io.Reader, _ map[string]string) error {
  40	z := parse.NewInput(r)
  41	ast, err := js.Parse(z, js.Options{WhileToFor: true})
  42	if err != nil {
  43		return err
  44	}
  45
  46	// license comments
  47	for _, comment := range ast.Comments {
  48		if 3 < len(comment) && comment[2] == '!' {
  49			w.Write(comment)
  50			if comment[1] == '/' {
  51				w.Write(newlineBytes)
  52			}
  53		} else if 2 < len(comment) && comment[0] == '#' && comment[1] == '!' {
  54			w.Write(comment)
  55		}
  56	}
  57
  58	m := &jsMinifier{
  59		o:       o,
  60		w:       w,
  61		renamer: newRenamer(!o.KeepVarNames, !o.useAlphabetVarNames),
  62	}
  63	m.hoistVars(&ast.BlockStmt)
  64	ast.List = optimizeStmtList(ast.List, functionBlock)
  65	for _, item := range ast.List {
  66		m.writeSemicolon()
  67		m.minifyStmt(item)
  68	}
  69
  70	if _, err := w.Write(nil); err != nil {
  71		return err
  72	}
  73	return nil
  74}
  75
  76type expectExpr int
  77
  78const (
  79	expectAny      expectExpr = iota
  80	expectExprStmt            // in statement
  81	expectExprBody            // in arrow function body
  82)
  83
  84type jsMinifier struct {
  85	o *Minifier
  86	w io.Writer
  87
  88	prev           []byte
  89	needsSemicolon bool       // write a semicolon if required
  90	needsSpace     bool       // write a space if next token is an identifier
  91	expectExpr     expectExpr // avoid ambiguous syntax such as an expression starting with function
  92	groupedStmt    bool       // avoid ambiguous syntax by grouping the expression statement
  93	inFor          bool
  94	spaceBefore    byte
  95
  96	renamer *renamer
  97}
  98
  99func (m *jsMinifier) write(b []byte) {
 100	// 0 < len(b)
 101	if m.needsSpace && js.IsIdentifierContinue(b) || m.spaceBefore == b[0] {
 102		m.w.Write(spaceBytes)
 103	}
 104	m.w.Write(b)
 105	m.prev = b
 106	m.needsSpace = false
 107	m.expectExpr = expectAny
 108	m.spaceBefore = 0
 109}
 110
 111func (m *jsMinifier) writeSpaceAfterIdent() {
 112	// space after identifier and after regular expression (to prevent confusion with its tag)
 113	if js.IsIdentifierEnd(m.prev) || 1 < len(m.prev) && m.prev[0] == '/' {
 114		m.w.Write(spaceBytes)
 115	}
 116}
 117
 118func (m *jsMinifier) writeSpaceBeforeIdent() {
 119	m.needsSpace = true
 120}
 121
 122func (m *jsMinifier) writeSpaceBefore(c byte) {
 123	m.spaceBefore = c
 124}
 125
 126func (m *jsMinifier) requireSemicolon() {
 127	m.needsSemicolon = true
 128}
 129
 130func (m *jsMinifier) writeSemicolon() {
 131	if m.needsSemicolon {
 132		m.w.Write(semicolonBytes)
 133		m.needsSemicolon = false
 134		m.needsSpace = false
 135	}
 136}
 137
 138func (m *jsMinifier) minifyStmt(i js.IStmt) {
 139	switch stmt := i.(type) {
 140	case *js.ExprStmt:
 141		m.expectExpr = expectExprStmt
 142		m.minifyExpr(stmt.Value, js.OpExpr)
 143		if m.groupedStmt {
 144			m.write(closeParenBytes)
 145			m.groupedStmt = false
 146		}
 147		m.requireSemicolon()
 148	case *js.VarDecl:
 149		m.minifyVarDecl(stmt, false)
 150		m.requireSemicolon()
 151	case *js.IfStmt:
 152		hasIf := !isEmptyStmt(stmt.Body)
 153		hasElse := !isEmptyStmt(stmt.Else)
 154		if !hasIf && !hasElse {
 155			break
 156		}
 157
 158		m.write(ifOpenBytes)
 159		m.minifyExpr(stmt.Cond, js.OpExpr)
 160		m.write(closeParenBytes)
 161
 162		if !hasIf && hasElse {
 163			m.requireSemicolon()
 164		} else if hasIf {
 165			if hasElse && endsInIf(stmt.Body) {
 166				// prevent: if(a){if(b)c}else d;  =>  if(a)if(b)c;else d;
 167				m.write(openBraceBytes)
 168				m.minifyStmt(stmt.Body)
 169				m.write(closeBraceBytes)
 170				m.needsSemicolon = false
 171			} else {
 172				m.minifyStmt(stmt.Body)
 173			}
 174		}
 175		if hasElse {
 176			m.writeSemicolon()
 177			m.write(elseBytes)
 178			m.writeSpaceBeforeIdent()
 179			m.minifyStmt(stmt.Else)
 180		}
 181	case *js.BlockStmt:
 182		m.renamer.renameScope(stmt.Scope)
 183		m.minifyBlockStmt(stmt)
 184	case *js.ReturnStmt:
 185		m.write(returnBytes)
 186		m.writeSpaceBeforeIdent()
 187		m.minifyExpr(stmt.Value, js.OpExpr)
 188		m.requireSemicolon()
 189	case *js.LabelledStmt:
 190		m.write(stmt.Label)
 191		m.write(colonBytes)
 192		m.minifyStmtOrBlock(stmt.Value, defaultBlock)
 193	case *js.BranchStmt:
 194		m.write(stmt.Type.Bytes())
 195		if stmt.Label != nil {
 196			m.write(spaceBytes)
 197			m.write(stmt.Label)
 198		}
 199		m.requireSemicolon()
 200	case *js.WithStmt:
 201		m.write(withOpenBytes)
 202		m.minifyExpr(stmt.Cond, js.OpExpr)
 203		m.write(closeParenBytes)
 204		m.minifyStmtOrBlock(stmt.Body, defaultBlock)
 205	case *js.DoWhileStmt:
 206		m.write(doBytes)
 207		m.writeSpaceBeforeIdent()
 208		m.minifyStmtOrBlock(stmt.Body, iterationBlock)
 209		m.writeSemicolon()
 210		m.write(whileOpenBytes)
 211		m.minifyExpr(stmt.Cond, js.OpExpr)
 212		m.write(closeParenBytes)
 213	case *js.WhileStmt:
 214		m.write(whileOpenBytes)
 215		m.minifyExpr(stmt.Cond, js.OpExpr)
 216		m.write(closeParenBytes)
 217		m.minifyStmtOrBlock(stmt.Body, iterationBlock)
 218	case *js.ForStmt:
 219		stmt.Body.List = optimizeStmtList(stmt.Body.List, iterationBlock)
 220		m.renamer.renameScope(stmt.Body.Scope)
 221		m.write(forOpenBytes)
 222		m.inFor = true
 223		if decl, ok := stmt.Init.(*js.VarDecl); ok {
 224			m.minifyVarDecl(decl, true)
 225		} else {
 226			m.minifyExpr(stmt.Init, js.OpLHS)
 227		}
 228		m.inFor = false
 229		m.write(semicolonBytes)
 230		m.minifyExpr(stmt.Cond, js.OpExpr)
 231		m.write(semicolonBytes)
 232		m.minifyExpr(stmt.Post, js.OpExpr)
 233		m.write(closeParenBytes)
 234		m.minifyBlockAsStmt(stmt.Body)
 235	case *js.ForInStmt:
 236		stmt.Body.List = optimizeStmtList(stmt.Body.List, iterationBlock)
 237		m.renamer.renameScope(stmt.Body.Scope)
 238		m.write(forOpenBytes)
 239		m.inFor = true
 240		if decl, ok := stmt.Init.(*js.VarDecl); ok {
 241			m.minifyVarDecl(decl, false)
 242		} else {
 243			m.minifyExpr(stmt.Init, js.OpLHS)
 244		}
 245		m.inFor = false
 246		m.writeSpaceAfterIdent()
 247		m.write(inBytes)
 248		m.writeSpaceBeforeIdent()
 249		m.minifyExpr(stmt.Value, js.OpExpr)
 250		m.write(closeParenBytes)
 251		m.minifyBlockAsStmt(stmt.Body)
 252	case *js.ForOfStmt:
 253		stmt.Body.List = optimizeStmtList(stmt.Body.List, iterationBlock)
 254		m.renamer.renameScope(stmt.Body.Scope)
 255		if stmt.Await {
 256			m.write(forAwaitOpenBytes)
 257		} else {
 258			m.write(forOpenBytes)
 259		}
 260		m.inFor = true
 261		if decl, ok := stmt.Init.(*js.VarDecl); ok {
 262			m.minifyVarDecl(decl, false)
 263		} else {
 264			m.minifyExpr(stmt.Init, js.OpLHS)
 265		}
 266		m.inFor = false
 267		m.writeSpaceAfterIdent()
 268		m.write(ofBytes)
 269		m.writeSpaceBeforeIdent()
 270		m.minifyExpr(stmt.Value, js.OpAssign)
 271		m.write(closeParenBytes)
 272		m.minifyBlockAsStmt(stmt.Body)
 273	case *js.SwitchStmt:
 274		m.write(switchOpenBytes)
 275		m.minifyExpr(stmt.Init, js.OpExpr)
 276		m.write(closeParenOpenBracketBytes)
 277		m.needsSemicolon = false
 278		for i, _ := range stmt.List {
 279			stmt.List[i].List = optimizeStmtList(stmt.List[i].List, defaultBlock)
 280		}
 281		m.renamer.renameScope(stmt.Scope)
 282		for _, clause := range stmt.List {
 283			m.writeSemicolon()
 284			m.write(clause.TokenType.Bytes())
 285			if clause.Cond != nil {
 286				m.writeSpaceBeforeIdent()
 287				m.minifyExpr(clause.Cond, js.OpExpr)
 288			}
 289			m.write(colonBytes)
 290			for _, item := range clause.List {
 291				m.writeSemicolon()
 292				m.minifyStmt(item)
 293			}
 294		}
 295		m.write(closeBraceBytes)
 296		m.needsSemicolon = false
 297	case *js.ThrowStmt:
 298		m.write(throwBytes)
 299		m.writeSpaceBeforeIdent()
 300		m.minifyExpr(stmt.Value, js.OpExpr)
 301		m.requireSemicolon()
 302	case *js.TryStmt:
 303		m.write(tryBytes)
 304		stmt.Body.List = optimizeStmtList(stmt.Body.List, defaultBlock)
 305		m.renamer.renameScope(stmt.Body.Scope)
 306		m.minifyBlockStmt(stmt.Body)
 307		if stmt.Catch != nil {
 308			m.write(catchBytes)
 309			stmt.Catch.List = optimizeStmtList(stmt.Catch.List, defaultBlock)
 310			if v, ok := stmt.Binding.(*js.Var); ok && v.Uses == 1 && m.o.minVersion(2019) {
 311				stmt.Catch.Scope.Declared = stmt.Catch.Scope.Declared[1:]
 312				stmt.Binding = nil
 313			}
 314			m.renamer.renameScope(stmt.Catch.Scope)
 315			if stmt.Binding != nil {
 316				m.write(openParenBytes)
 317				m.minifyBinding(stmt.Binding)
 318				m.write(closeParenBytes)
 319			}
 320			m.minifyBlockStmt(stmt.Catch)
 321		}
 322		if stmt.Finally != nil {
 323			m.write(finallyBytes)
 324			stmt.Finally.List = optimizeStmtList(stmt.Finally.List, defaultBlock)
 325			m.renamer.renameScope(stmt.Finally.Scope)
 326			m.minifyBlockStmt(stmt.Finally)
 327		}
 328	case *js.FuncDecl:
 329		m.minifyFuncDecl(stmt, false)
 330	case *js.ClassDecl:
 331		m.minifyClassDecl(stmt)
 332	case *js.DebuggerStmt:
 333		m.write(debuggerBytes)
 334		m.requireSemicolon()
 335	case *js.EmptyStmt:
 336	case *js.ImportStmt:
 337		m.write(importBytes)
 338		if stmt.Default != nil {
 339			m.write(spaceBytes)
 340			m.write(stmt.Default)
 341			if len(stmt.List) != 0 {
 342				m.write(commaBytes)
 343			} else if stmt.Default != nil || len(stmt.List) != 0 {
 344				m.write(spaceBytes)
 345			}
 346		}
 347		if len(stmt.List) == 1 && len(stmt.List[0].Name) == 1 && stmt.List[0].Name[0] == '*' {
 348			m.writeSpaceBeforeIdent()
 349			m.minifyAlias(stmt.List[0])
 350			if stmt.Default != nil || len(stmt.List) != 0 {
 351				m.write(spaceBytes)
 352			}
 353		} else if 0 < len(stmt.List) {
 354			m.write(openBraceBytes)
 355			for i, item := range stmt.List {
 356				if i != 0 {
 357					m.write(commaBytes)
 358				}
 359				m.minifyAlias(item)
 360			}
 361			m.write(closeBraceBytes)
 362		}
 363		if stmt.Default != nil || len(stmt.List) != 0 {
 364			m.write(fromBytes)
 365		}
 366		m.write(minifyString(stmt.Module, false))
 367		m.requireSemicolon()
 368	case *js.ExportStmt:
 369		m.write(exportBytes)
 370		if stmt.Decl != nil {
 371			if stmt.Default {
 372				m.write(spaceDefaultBytes)
 373				m.writeSpaceBeforeIdent()
 374				m.minifyExpr(stmt.Decl, js.OpAssign)
 375				_, isHoistable := stmt.Decl.(*js.FuncDecl)
 376				_, isClass := stmt.Decl.(*js.ClassDecl)
 377				if !isHoistable && !isClass {
 378					m.requireSemicolon()
 379				}
 380			} else {
 381				m.writeSpaceBeforeIdent()
 382				m.minifyStmt(stmt.Decl.(js.IStmt)) // can only be variable, function, or class decl
 383			}
 384		} else {
 385			if len(stmt.List) == 1 && (len(stmt.List[0].Name) == 1 && stmt.List[0].Name[0] == '*' || stmt.List[0].Name == nil && len(stmt.List[0].Binding) == 1 && stmt.List[0].Binding[0] == '*') {
 386				m.writeSpaceBeforeIdent()
 387				m.minifyAlias(stmt.List[0])
 388				if stmt.Module != nil && stmt.List[0].Name != nil {
 389					m.write(spaceBytes)
 390				}
 391			} else if 0 < len(stmt.List) {
 392				m.write(openBraceBytes)
 393				for i, item := range stmt.List {
 394					if i != 0 {
 395						m.write(commaBytes)
 396					}
 397					m.minifyAlias(item)
 398				}
 399				m.write(closeBraceBytes)
 400			}
 401			if stmt.Module != nil {
 402				m.write(fromBytes)
 403				m.write(minifyString(stmt.Module, false))
 404			}
 405			m.requireSemicolon()
 406		}
 407	case *js.DirectivePrologueStmt:
 408		stmt.Value[0] = '"'
 409		stmt.Value[len(stmt.Value)-1] = '"'
 410		m.write(stmt.Value)
 411		m.requireSemicolon()
 412	}
 413}
 414
 415func (m *jsMinifier) minifyBlockStmt(stmt *js.BlockStmt) {
 416	m.write(openBraceBytes)
 417	m.needsSemicolon = false
 418	for _, item := range stmt.List {
 419		m.writeSemicolon()
 420		m.minifyStmt(item)
 421	}
 422	m.write(closeBraceBytes)
 423	m.needsSemicolon = false
 424}
 425
 426func (m *jsMinifier) minifyBlockAsStmt(blockStmt *js.BlockStmt) {
 427	// minify block when statement is expected, i.e. semicolon if empty or remove braces for single statement
 428	// assume we already renamed the scope
 429	hasLexicalVars := false
 430	for _, v := range blockStmt.Scope.Declared[blockStmt.Scope.NumForDecls:] {
 431		if v.Decl == js.LexicalDecl {
 432			hasLexicalVars = true
 433			break
 434		}
 435	}
 436	if 1 < len(blockStmt.List) || hasLexicalVars {
 437		m.minifyBlockStmt(blockStmt)
 438	} else if len(blockStmt.List) == 1 {
 439		m.minifyStmt(blockStmt.List[0])
 440	} else {
 441		m.write(semicolonBytes)
 442		m.needsSemicolon = false
 443	}
 444}
 445
 446func (m *jsMinifier) minifyStmtOrBlock(i js.IStmt, blockType blockType) {
 447	// minify stmt or a block
 448	if blockStmt, ok := i.(*js.BlockStmt); ok {
 449		blockStmt.List = optimizeStmtList(blockStmt.List, blockType)
 450		m.renamer.renameScope(blockStmt.Scope)
 451		m.minifyBlockAsStmt(blockStmt)
 452	} else {
 453		// optimizeStmtList can in some cases expand one stmt to two shorter stmts
 454		list := optimizeStmtList([]js.IStmt{i}, blockType)
 455		if len(list) == 1 {
 456			m.minifyStmt(list[0])
 457		} else if len(list) == 0 {
 458			m.write(semicolonBytes)
 459			m.needsSemicolon = false
 460		} else {
 461			m.minifyBlockStmt(&js.BlockStmt{List: list, Scope: js.Scope{}})
 462		}
 463	}
 464}
 465
 466func (m *jsMinifier) minifyAlias(alias js.Alias) {
 467	if alias.Name != nil {
 468		if alias.Name[0] == '"' || alias.Name[0] == '\'' {
 469			m.write(minifyString(alias.Name, false))
 470		} else {
 471			m.write(alias.Name)
 472		}
 473		if !bytes.Equal(alias.Name, starBytes) {
 474			m.write(spaceBytes)
 475		}
 476		m.write(asSpaceBytes)
 477	}
 478	if alias.Binding != nil {
 479		if alias.Binding[0] == '"' || alias.Binding[0] == '\'' {
 480			m.write(minifyString(alias.Binding, false))
 481		} else {
 482			m.write(alias.Binding)
 483		}
 484	}
 485}
 486
 487func (m *jsMinifier) minifyParams(params js.Params, removeUnused bool) {
 488	// remove unused parameters from the end
 489	j := len(params.List)
 490	if removeUnused && params.Rest == nil {
 491		for ; 0 < j; j-- {
 492			if v, ok := params.List[j-1].Binding.(*js.Var); !ok || ok && 1 < v.Uses {
 493				break
 494			}
 495		}
 496	}
 497
 498	m.write(openParenBytes)
 499	for i, item := range params.List[:j] {
 500		if i != 0 {
 501			m.write(commaBytes)
 502		}
 503		m.minifyBindingElement(item)
 504	}
 505	if params.Rest != nil {
 506		if len(params.List) != 0 {
 507			m.write(commaBytes)
 508		}
 509		m.write(ellipsisBytes)
 510		m.minifyBinding(params.Rest)
 511	}
 512	m.write(closeParenBytes)
 513}
 514
 515func (m *jsMinifier) minifyArguments(args js.Args) {
 516	m.write(openParenBytes)
 517	for i, item := range args.List {
 518		if i != 0 {
 519			m.write(commaBytes)
 520		}
 521		if item.Rest {
 522			m.write(ellipsisBytes)
 523		}
 524		m.minifyExpr(item.Value, js.OpAssign)
 525	}
 526	m.write(closeParenBytes)
 527}
 528
 529func (m *jsMinifier) minifyVarDecl(decl *js.VarDecl, onlyDefines bool) {
 530	if len(decl.List) == 0 {
 531		return
 532	} else if decl.TokenType == js.ErrorToken {
 533		// remove 'var' when hoisting variables
 534		first := true
 535		for _, item := range decl.List {
 536			if item.Default != nil || !onlyDefines {
 537				if !first {
 538					m.write(commaBytes)
 539				}
 540				m.minifyBindingElement(item)
 541				first = false
 542			}
 543		}
 544	} else {
 545		if decl.TokenType == js.VarToken && len(decl.List) <= 10000 {
 546			// move single var decls forward and order for GZIP optimization
 547			start := 0
 548			if _, ok := decl.List[0].Binding.(*js.Var); !ok {
 549				start++
 550			}
 551			for i := 0; i < len(decl.List); i++ {
 552				item := decl.List[i]
 553				if v, ok := item.Binding.(*js.Var); ok && item.Default == nil && len(v.Data) == 1 {
 554					for j := start; j < len(decl.List); j++ {
 555						if v2, ok := decl.List[j].Binding.(*js.Var); ok && decl.List[j].Default == nil && len(v2.Data) == 1 {
 556							if m.renamer.identOrder[v2.Data[0]] < m.renamer.identOrder[v.Data[0]] {
 557								continue
 558							} else if m.renamer.identOrder[v2.Data[0]] == m.renamer.identOrder[v.Data[0]] {
 559								break
 560							}
 561						}
 562						decl.List = append(decl.List[:i], decl.List[i+1:]...)
 563						decl.List = append(decl.List[:j], append([]js.BindingElement{item}, decl.List[j:]...)...)
 564						break
 565					}
 566				}
 567			}
 568		}
 569
 570		m.write(decl.TokenType.Bytes())
 571		m.writeSpaceBeforeIdent()
 572		for i, item := range decl.List {
 573			if i != 0 {
 574				m.write(commaBytes)
 575			}
 576			m.minifyBindingElement(item)
 577		}
 578	}
 579}
 580
 581func (m *jsMinifier) minifyFuncDecl(decl *js.FuncDecl, inExpr bool) {
 582	parentRename := m.renamer.rename
 583	m.renamer.rename = !decl.Body.Scope.HasWith && !m.o.KeepVarNames
 584	m.hoistVars(&decl.Body)
 585	decl.Body.List = optimizeStmtList(decl.Body.List, functionBlock)
 586
 587	if decl.Async {
 588		m.write(asyncSpaceBytes)
 589	}
 590	m.write(functionBytes)
 591	if decl.Generator {
 592		m.write(starBytes)
 593	}
 594
 595	// TODO: remove function name, really necessary?
 596	//if decl.Name != nil && decl.Name.Uses == 1 {
 597	//	scope := decl.Body.Scope
 598	//	for i, vorig := range scope.Declared {
 599	//		if decl.Name == vorig {
 600	//			scope.Declared = append(scope.Declared[:i], scope.Declared[i+1:]...)
 601	//		}
 602	//	}
 603	//}
 604
 605	if inExpr {
 606		m.renamer.renameScope(decl.Body.Scope)
 607	}
 608	if decl.Name != nil && (!inExpr || 1 < decl.Name.Uses) {
 609		if !decl.Generator {
 610			m.write(spaceBytes)
 611		}
 612		m.write(decl.Name.Data)
 613	}
 614	if !inExpr {
 615		m.renamer.renameScope(decl.Body.Scope)
 616	}
 617
 618	m.minifyParams(decl.Params, true)
 619	m.minifyBlockStmt(&decl.Body)
 620	m.renamer.rename = parentRename
 621}
 622
 623func (m *jsMinifier) minifyMethodDecl(decl *js.MethodDecl) {
 624	parentRename := m.renamer.rename
 625	m.renamer.rename = !decl.Body.Scope.HasWith && !m.o.KeepVarNames
 626	m.hoistVars(&decl.Body)
 627	decl.Body.List = optimizeStmtList(decl.Body.List, functionBlock)
 628
 629	if decl.Static {
 630		m.write(staticBytes)
 631		m.writeSpaceBeforeIdent()
 632	}
 633	if decl.Async {
 634		m.write(asyncBytes)
 635		if decl.Generator {
 636			m.write(starBytes)
 637		} else {
 638			m.writeSpaceBeforeIdent()
 639		}
 640	} else if decl.Generator {
 641		m.write(starBytes)
 642	} else if decl.Get {
 643		m.write(getBytes)
 644		m.writeSpaceBeforeIdent()
 645	} else if decl.Set {
 646		m.write(setBytes)
 647		m.writeSpaceBeforeIdent()
 648	}
 649	m.minifyPropertyName(decl.Name)
 650	m.renamer.renameScope(decl.Body.Scope)
 651	m.minifyParams(decl.Params, !decl.Set)
 652	m.minifyBlockStmt(&decl.Body)
 653	m.renamer.rename = parentRename
 654}
 655
 656func (m *jsMinifier) minifyArrowFunc(decl *js.ArrowFunc) {
 657	parentRename := m.renamer.rename
 658	m.renamer.rename = !decl.Body.Scope.HasWith && !m.o.KeepVarNames
 659	m.hoistVars(&decl.Body)
 660	decl.Body.List = optimizeStmtList(decl.Body.List, functionBlock)
 661
 662	m.renamer.renameScope(decl.Body.Scope)
 663	if decl.Async {
 664		m.write(asyncBytes)
 665	}
 666	removeParens := false
 667	if decl.Params.Rest == nil && len(decl.Params.List) == 1 && decl.Params.List[0].Default == nil {
 668		if decl.Params.List[0].Binding == nil {
 669			removeParens = true
 670		} else if _, ok := decl.Params.List[0].Binding.(*js.Var); ok {
 671			removeParens = true
 672		}
 673	}
 674	if removeParens {
 675		if decl.Async && decl.Params.List[0].Binding != nil {
 676			// add space after async in: async a => ...
 677			m.write(spaceBytes)
 678		}
 679		m.minifyBindingElement(decl.Params.List[0])
 680	} else {
 681		parentInFor := m.inFor
 682		m.inFor = false
 683		m.minifyParams(decl.Params, true)
 684		m.inFor = parentInFor
 685	}
 686	m.write(arrowBytes)
 687	removeBraces := false
 688	if 0 < len(decl.Body.List) {
 689		returnStmt, isReturn := decl.Body.List[len(decl.Body.List)-1].(*js.ReturnStmt)
 690		if isReturn && returnStmt.Value != nil {
 691			// merge expression statements to final return statement, remove function body braces
 692			var list []js.IExpr
 693			removeBraces = true
 694			for _, item := range decl.Body.List[:len(decl.Body.List)-1] {
 695				if expr, isExpr := item.(*js.ExprStmt); isExpr {
 696					list = append(list, expr.Value)
 697				} else {
 698					removeBraces = false
 699					break
 700				}
 701			}
 702			if removeBraces {
 703				list = append(list, returnStmt.Value)
 704				expr := list[0]
 705				if 0 < len(list) {
 706					if 1 < len(list) {
 707						expr = &js.CommaExpr{list}
 708					}
 709					expr = &js.GroupExpr{X: expr}
 710				}
 711				m.expectExpr = expectExprBody
 712				m.minifyExpr(expr, js.OpAssign)
 713				if m.groupedStmt {
 714					m.write(closeParenBytes)
 715					m.groupedStmt = false
 716				}
 717			}
 718		} else if isReturn && returnStmt.Value == nil {
 719			// remove empty return
 720			decl.Body.List = decl.Body.List[:len(decl.Body.List)-1]
 721		}
 722	}
 723	if !removeBraces {
 724		m.minifyBlockStmt(&decl.Body)
 725	}
 726	m.renamer.rename = parentRename
 727}
 728
 729func (m *jsMinifier) minifyClassDecl(decl *js.ClassDecl) {
 730	m.write(classBytes)
 731	if decl.Name != nil {
 732		m.write(spaceBytes)
 733		m.write(decl.Name.Data)
 734	}
 735	if decl.Extends != nil {
 736		m.write(spaceExtendsBytes)
 737		m.writeSpaceBeforeIdent()
 738		m.minifyExpr(decl.Extends, js.OpLHS)
 739	}
 740	m.write(openBraceBytes)
 741	m.needsSemicolon = false
 742	for _, item := range decl.List {
 743		m.writeSemicolon()
 744		if item.StaticBlock != nil {
 745			m.write(staticBytes)
 746			m.minifyBlockStmt(item.StaticBlock)
 747		} else if item.Method != nil {
 748			m.minifyMethodDecl(item.Method)
 749		} else {
 750			if item.Static {
 751				m.write(staticBytes)
 752				if !item.Name.IsComputed() && item.Name.Literal.TokenType == js.IdentifierToken {
 753					m.write(spaceBytes)
 754				}
 755			}
 756			m.minifyPropertyName(item.Name)
 757			if item.Init != nil {
 758				m.write(equalBytes)
 759				m.minifyExpr(item.Init, js.OpAssign)
 760			}
 761			m.requireSemicolon()
 762		}
 763	}
 764	m.write(closeBraceBytes)
 765	m.needsSemicolon = false
 766}
 767
 768func (m *jsMinifier) minifyPropertyName(name js.PropertyName) {
 769	if name.IsComputed() {
 770		m.write(openBracketBytes)
 771		m.minifyExpr(name.Computed, js.OpAssign)
 772		m.write(closeBracketBytes)
 773	} else if name.Literal.TokenType == js.StringToken {
 774		m.write(minifyString(name.Literal.Data, false))
 775	} else {
 776		m.write(name.Literal.Data)
 777	}
 778}
 779
 780func (m *jsMinifier) minifyProperty(property js.Property) {
 781	// property.Name is always set in ObjectLiteral
 782	if property.Spread {
 783		m.write(ellipsisBytes)
 784	} else if v, ok := property.Value.(*js.Var); property.Name != nil && (!ok || !property.Name.IsIdent(v.Name())) {
 785		// add 'old-name:' before BindingName as the latter will be renamed
 786		m.minifyPropertyName(*property.Name)
 787		m.write(colonBytes)
 788	}
 789	m.minifyExpr(property.Value, js.OpAssign)
 790	if property.Init != nil {
 791		m.write(equalBytes)
 792		m.minifyExpr(property.Init, js.OpAssign)
 793	}
 794}
 795
 796func (m *jsMinifier) minifyBindingElement(element js.BindingElement) {
 797	if element.Binding != nil {
 798		parentInFor := m.inFor
 799		m.inFor = false
 800		m.minifyBinding(element.Binding)
 801		m.inFor = parentInFor
 802		if element.Default != nil {
 803			m.write(equalBytes)
 804			m.minifyExpr(element.Default, js.OpAssign)
 805		}
 806	}
 807}
 808
 809func (m *jsMinifier) minifyBinding(ibinding js.IBinding) {
 810	switch binding := ibinding.(type) {
 811	case *js.Var:
 812		m.write(binding.Data)
 813	case *js.BindingArray:
 814		m.write(openBracketBytes)
 815		for i, item := range binding.List {
 816			if i != 0 {
 817				m.write(commaBytes)
 818			}
 819			m.minifyBindingElement(item)
 820		}
 821		if binding.Rest != nil {
 822			if 0 < len(binding.List) {
 823				m.write(commaBytes)
 824			}
 825			m.write(ellipsisBytes)
 826			m.minifyBinding(binding.Rest)
 827		}
 828		m.write(closeBracketBytes)
 829	case *js.BindingObject:
 830		m.write(openBraceBytes)
 831		for i, item := range binding.List {
 832			if i != 0 {
 833				m.write(commaBytes)
 834			}
 835			// item.Key is always set
 836			if item.Key.IsComputed() {
 837				m.minifyPropertyName(*item.Key)
 838				m.write(colonBytes)
 839			} else if v, ok := item.Value.Binding.(*js.Var); !ok || !item.Key.IsIdent(v.Data) {
 840				// add 'old-name:' before BindingName as the latter will be renamed
 841				m.minifyPropertyName(*item.Key)
 842				m.write(colonBytes)
 843			}
 844			m.minifyBindingElement(item.Value)
 845		}
 846		if binding.Rest != nil {
 847			if 0 < len(binding.List) {
 848				m.write(commaBytes)
 849			}
 850			m.write(ellipsisBytes)
 851			m.write(binding.Rest.Data)
 852		}
 853		m.write(closeBraceBytes)
 854	}
 855}
 856
 857func (m *jsMinifier) minifyExpr(i js.IExpr, prec js.OpPrec) {
 858	if cond, ok := i.(*js.CondExpr); ok {
 859		i = m.optimizeCondExpr(cond, prec)
 860	} else if unary, ok := i.(*js.UnaryExpr); ok {
 861		i = optimizeUnaryExpr(unary, prec)
 862	}
 863
 864	switch expr := i.(type) {
 865	case *js.Var:
 866		for expr.Link != nil {
 867			expr = expr.Link
 868		}
 869		data := expr.Data
 870		if bytes.Equal(data, undefinedBytes) { // TODO: only if not defined
 871			if js.OpUnary < prec {
 872				m.write(groupedVoidZeroBytes)
 873			} else {
 874				m.write(voidZeroBytes)
 875			}
 876		} else if bytes.Equal(data, infinityBytes) { // TODO: only if not defined
 877			if js.OpMul < prec {
 878				m.write(groupedOneDivZeroBytes)
 879			} else {
 880				m.write(oneDivZeroBytes)
 881			}
 882		} else {
 883			m.write(data)
 884		}
 885	case *js.LiteralExpr:
 886		if expr.TokenType == js.DecimalToken {
 887			m.write(decimalNumber(expr.Data, m.o.Precision))
 888		} else if expr.TokenType == js.BinaryToken {
 889			m.write(binaryNumber(expr.Data, m.o.Precision))
 890		} else if expr.TokenType == js.OctalToken {
 891			m.write(octalNumber(expr.Data, m.o.Precision))
 892		} else if expr.TokenType == js.HexadecimalToken {
 893			m.write(hexadecimalNumber(expr.Data, m.o.Precision))
 894		} else if expr.TokenType == js.TrueToken {
 895			if js.OpUnary < prec {
 896				m.write(groupedNotZeroBytes)
 897			} else {
 898				m.write(notZeroBytes)
 899			}
 900		} else if expr.TokenType == js.FalseToken {
 901			if js.OpUnary < prec {
 902				m.write(groupedNotOneBytes)
 903			} else {
 904				m.write(notOneBytes)
 905			}
 906		} else if expr.TokenType == js.StringToken {
 907			m.write(minifyString(expr.Data, true))
 908		} else if expr.TokenType == js.RegExpToken {
 909			// </script>/ => < /script>/
 910			if 0 < len(m.prev) && m.prev[len(m.prev)-1] == '<' && bytes.HasPrefix(expr.Data, regExpScriptBytes) {
 911				m.write(spaceBytes)
 912			}
 913			m.write(minifyRegExp(expr.Data))
 914		} else {
 915			m.write(expr.Data)
 916		}
 917	case *js.BinaryExpr:
 918		mergeBinaryExpr(expr)
 919		if expr.X == nil {
 920			m.minifyExpr(expr.Y, prec)
 921			break
 922		}
 923
 924		precLeft := binaryLeftPrecMap[expr.Op]
 925		// convert (a,b)&&c into a,b&&c but not a=(b,c)&&d into a=(b,c&&d)
 926		if prec <= js.OpExpr {
 927			if group, ok := expr.X.(*js.GroupExpr); ok {
 928				if comma, ok := group.X.(*js.CommaExpr); ok && js.OpAnd <= exprPrec(comma.List[len(comma.List)-1]) {
 929					expr.X = group.X
 930					precLeft = js.OpExpr
 931				}
 932			}
 933		}
 934		if expr.Op == js.InstanceofToken || expr.Op == js.InToken {
 935			group := expr.Op == js.InToken && m.inFor
 936			if group {
 937				m.write(openParenBytes)
 938			}
 939			m.minifyExpr(expr.X, precLeft)
 940			m.writeSpaceAfterIdent()
 941			m.write(expr.Op.Bytes())
 942			m.writeSpaceBeforeIdent()
 943			m.minifyExpr(expr.Y, binaryRightPrecMap[expr.Op])
 944			if group {
 945				m.write(closeParenBytes)
 946			}
 947		} else {
 948			// TODO: has effect on GZIP?
 949			//if expr.Op == js.EqEqToken || expr.Op == js.NotEqToken || expr.Op == js.EqEqEqToken || expr.Op == js.NotEqEqToken {
 950			//	// switch a==const for const==a, such as typeof a=="undefined" for "undefined"==typeof a (GZIP improvement)
 951			//	if _, ok := expr.Y.(*js.LiteralExpr); ok {
 952			//		expr.X, expr.Y = expr.Y, expr.X
 953			//	}
 954			//}
 955
 956			if v, not, ok := isUndefinedOrNullVar(expr); ok {
 957				// change a===null||a===undefined to a==null
 958				op := js.EqEqToken
 959				if not {
 960					op = js.NotEqToken
 961				}
 962				expr = &js.BinaryExpr{op, v, &js.LiteralExpr{js.NullToken, nullBytes}}
 963			}
 964
 965			m.minifyExpr(expr.X, precLeft)
 966			if expr.Op == js.GtToken && m.prev[len(m.prev)-1] == '-' {
 967				// 0 < len(m.prev) always
 968				m.write(spaceBytes)
 969			} else if expr.Op == js.EqEqEqToken || expr.Op == js.NotEqEqToken {
 970				if left, ok := expr.X.(*js.UnaryExpr); ok && left.Op == js.TypeofToken {
 971					if right, ok := expr.Y.(*js.LiteralExpr); ok && right.TokenType == js.StringToken {
 972						if expr.Op == js.EqEqEqToken {
 973							expr.Op = js.EqEqToken
 974						} else {
 975							expr.Op = js.NotEqToken
 976						}
 977					}
 978				} else if right, ok := expr.Y.(*js.UnaryExpr); ok && right.Op == js.TypeofToken {
 979					if left, ok := expr.X.(*js.LiteralExpr); ok && left.TokenType == js.StringToken {
 980						if expr.Op == js.EqEqEqToken {
 981							expr.Op = js.EqEqToken
 982						} else {
 983							expr.Op = js.NotEqToken
 984						}
 985					}
 986				}
 987			}
 988			m.write(expr.Op.Bytes())
 989			if expr.Op == js.AddToken {
 990				// +++  =>  + ++
 991				m.writeSpaceBefore('+')
 992			} else if expr.Op == js.SubToken {
 993				// ---  =>  - --
 994				m.writeSpaceBefore('-')
 995			} else if expr.Op == js.DivToken {
 996				// //  =>  / /
 997				m.writeSpaceBefore('/')
 998			}
 999			m.minifyExpr(expr.Y, binaryRightPrecMap[expr.Op])
1000		}
1001	case *js.UnaryExpr:
1002		if expr.Op == js.PostIncrToken || expr.Op == js.PostDecrToken {
1003			m.minifyExpr(expr.X, unaryPrecMap[expr.Op])
1004			m.write(expr.Op.Bytes())
1005		} else {
1006			isLtNot := expr.Op == js.NotToken && 0 < len(m.prev) && m.prev[len(m.prev)-1] == '<'
1007			m.write(expr.Op.Bytes())
1008			if expr.Op == js.DeleteToken || expr.Op == js.VoidToken || expr.Op == js.TypeofToken || expr.Op == js.AwaitToken {
1009				m.writeSpaceBeforeIdent()
1010			} else if expr.Op == js.PosToken {
1011				// +++  =>  + ++
1012				m.writeSpaceBefore('+')
1013			} else if expr.Op == js.NegToken || isLtNot {
1014				// ---  =>  - --
1015				// <!--  =>  <! --
1016				m.writeSpaceBefore('-')
1017			} else if expr.Op == js.NotToken {
1018				if lit, ok := expr.X.(*js.LiteralExpr); ok && (lit.TokenType == js.StringToken || lit.TokenType == js.RegExpToken) {
1019					// !"string"  =>  !1
1020					m.write(oneBytes)
1021					break
1022				} else if ok && lit.TokenType == js.DecimalToken {
1023					// !123  =>  !1 (except for !0)
1024					if num := minify.Number(lit.Data, m.o.Precision); len(num) == 1 && num[0] == '0' {
1025						m.write(zeroBytes)
1026					} else {
1027						m.write(oneBytes)
1028					}
1029					break
1030				}
1031			}
1032			m.minifyExpr(expr.X, unaryPrecMap[expr.Op])
1033		}
1034	case *js.DotExpr:
1035		if group, ok := expr.X.(*js.GroupExpr); ok {
1036			if lit, ok := group.X.(*js.LiteralExpr); ok && lit.TokenType == js.DecimalToken {
1037				num := minify.Number(lit.Data, m.o.Precision)
1038				isInt := true
1039				for _, c := range num {
1040					if c == '.' || c == 'e' || c == 'E' {
1041						isInt = false
1042						break
1043					}
1044				}
1045				if isInt {
1046					m.write(num)
1047					m.write(dotBytes)
1048				} else {
1049					m.write(num)
1050				}
1051				m.write(dotBytes)
1052				m.write(expr.Y.Data)
1053				break
1054			}
1055		}
1056		if prec < js.OpMember {
1057			m.minifyExpr(expr.X, js.OpCall)
1058		} else {
1059			m.minifyExpr(expr.X, js.OpMember)
1060		}
1061		if expr.Optional {
1062			m.write(questionBytes)
1063		} else if last := m.prev[len(m.prev)-1]; '0' <= last && last <= '9' {
1064			// 0 < len(m.prev) always
1065			isInteger := true
1066			for _, c := range m.prev[:len(m.prev)-1] {
1067				if c < '0' || '9' < c {
1068					isInteger = false
1069					break
1070				}
1071			}
1072			if isInteger {
1073				// prevent previous integer
1074				m.write(dotBytes)
1075			}
1076		}
1077		m.write(dotBytes)
1078		m.write(expr.Y.Data)
1079	case *js.GroupExpr:
1080		if cond, ok := expr.X.(*js.CondExpr); ok {
1081			expr.X = m.optimizeCondExpr(cond, js.OpExpr)
1082		}
1083		precInside := exprPrec(expr.X)
1084		if prec <= precInside || precInside == js.OpCoalesce && prec == js.OpBitOr {
1085			m.minifyExpr(expr.X, prec)
1086		} else {
1087			parentInFor := m.inFor
1088			m.inFor = false
1089			m.write(openParenBytes)
1090			m.minifyExpr(expr.X, js.OpExpr)
1091			m.write(closeParenBytes)
1092			m.inFor = parentInFor
1093		}
1094	case *js.ArrayExpr:
1095		parentInFor := m.inFor
1096		m.inFor = false
1097		m.write(openBracketBytes)
1098		for i, item := range expr.List {
1099			if i != 0 {
1100				m.write(commaBytes)
1101			}
1102			if item.Spread {
1103				m.write(ellipsisBytes)
1104			}
1105			m.minifyExpr(item.Value, js.OpAssign)
1106		}
1107		if 0 < len(expr.List) && expr.List[len(expr.List)-1].Value == nil {
1108			m.write(commaBytes)
1109		}
1110		m.write(closeBracketBytes)
1111		m.inFor = parentInFor
1112	case *js.ObjectExpr:
1113		parentInFor := m.inFor
1114		m.inFor = false
1115		groupedStmt := m.expectExpr != expectAny
1116		if groupedStmt {
1117			m.write(openParenBracketBytes)
1118		} else {
1119			m.write(openBraceBytes)
1120		}
1121		for i, item := range expr.List {
1122			if i != 0 {
1123				m.write(commaBytes)
1124			}
1125			m.minifyProperty(item)
1126		}
1127		m.write(closeBraceBytes)
1128		if groupedStmt {
1129			m.groupedStmt = true
1130		}
1131		m.inFor = parentInFor
1132	case *js.TemplateExpr:
1133		if expr.Tag != nil {
1134			if prec < js.OpMember {
1135				m.minifyExpr(expr.Tag, js.OpCall)
1136			} else {
1137				m.minifyExpr(expr.Tag, js.OpMember)
1138			}
1139			if expr.Optional {
1140				m.write(optChainBytes)
1141			}
1142		}
1143		parentInFor := m.inFor
1144		m.inFor = false
1145		for _, item := range expr.List {
1146			m.write(replaceEscapes(item.Value, '`', 1, 2))
1147			m.minifyExpr(item.Expr, js.OpExpr)
1148		}
1149		m.write(replaceEscapes(expr.Tail, '`', 1, 1))
1150		m.inFor = parentInFor
1151	case *js.NewExpr:
1152		if expr.Args == nil && js.OpLHS < prec && prec != js.OpNew {
1153			m.write(openNewBytes)
1154			m.writeSpaceBeforeIdent()
1155			m.minifyExpr(expr.X, js.OpNew)
1156			m.write(closeParenBytes)
1157		} else {
1158			m.write(newBytes)
1159			m.writeSpaceBeforeIdent()
1160			if expr.Args != nil {
1161				m.minifyExpr(expr.X, js.OpMember)
1162				m.minifyArguments(*expr.Args)
1163			} else {
1164				m.minifyExpr(expr.X, js.OpNew)
1165			}
1166		}
1167	case *js.NewTargetExpr:
1168		m.write(newTargetBytes)
1169		m.writeSpaceBeforeIdent()
1170	case *js.ImportMetaExpr:
1171		if m.expectExpr == expectExprStmt {
1172			m.write(openParenBytes)
1173			m.groupedStmt = true
1174		}
1175		m.write(importMetaBytes)
1176		m.writeSpaceBeforeIdent()
1177	case *js.YieldExpr:
1178		m.write(yieldBytes)
1179		m.writeSpaceBeforeIdent()
1180		if expr.X != nil {
1181			if expr.Generator {
1182				m.write(starBytes)
1183				m.minifyExpr(expr.X, js.OpAssign)
1184			} else if v, ok := expr.X.(*js.Var); !ok || !bytes.Equal(v.Name(), undefinedBytes) { // TODO: only if not defined
1185				m.minifyExpr(expr.X, js.OpAssign)
1186			}
1187		}
1188	case *js.CallExpr:
1189		m.minifyExpr(expr.X, js.OpCall)
1190		parentInFor := m.inFor
1191		m.inFor = false
1192		if expr.Optional {
1193			m.write(optChainBytes)
1194		}
1195		m.minifyArguments(expr.Args)
1196		m.inFor = parentInFor
1197	case *js.IndexExpr:
1198		if m.expectExpr == expectExprStmt {
1199			if v, ok := expr.X.(*js.Var); ok && bytes.Equal(v.Name(), letBytes) {
1200				m.write(notBytes)
1201			}
1202		}
1203		if prec < js.OpMember {
1204			m.minifyExpr(expr.X, js.OpCall)
1205		} else {
1206			m.minifyExpr(expr.X, js.OpMember)
1207		}
1208		if expr.Optional {
1209			m.write(optChainBytes)
1210		}
1211		if lit, ok := expr.Y.(*js.LiteralExpr); ok && lit.TokenType == js.StringToken && 2 < len(lit.Data) {
1212			if isIdent := js.AsIdentifierName(lit.Data[1 : len(lit.Data)-1]); isIdent {
1213				m.write(dotBytes)
1214				m.write(lit.Data[1 : len(lit.Data)-1])
1215				break
1216			} else if isNum := js.AsDecimalLiteral(lit.Data[1 : len(lit.Data)-1]); isNum {
1217				m.write(openBracketBytes)
1218				m.write(minify.Number(lit.Data[1:len(lit.Data)-1], 0))
1219				m.write(closeBracketBytes)
1220				break
1221			}
1222		}
1223		parentInFor := m.inFor
1224		m.inFor = false
1225		m.write(openBracketBytes)
1226		m.minifyExpr(expr.Y, js.OpExpr)
1227		m.write(closeBracketBytes)
1228		m.inFor = parentInFor
1229	case *js.CondExpr:
1230		m.minifyExpr(expr.Cond, js.OpCoalesce)
1231		m.write(questionBytes)
1232		m.minifyExpr(expr.X, js.OpAssign)
1233		m.write(colonBytes)
1234		m.minifyExpr(expr.Y, js.OpAssign)
1235	case *js.VarDecl:
1236		m.minifyVarDecl(expr, true) // happens in for statement or when vars were hoisted
1237	case *js.FuncDecl:
1238		grouped := m.expectExpr == expectExprStmt && prec != js.OpExpr
1239		if grouped {
1240			m.write(openParenBytes)
1241		} else if m.expectExpr == expectExprStmt {
1242			m.write(notBytes)
1243		}
1244		parentInFor, parentGroupedStmt := m.inFor, m.groupedStmt
1245		m.inFor, m.groupedStmt = false, false
1246		m.minifyFuncDecl(expr, true)
1247		m.inFor, m.groupedStmt = parentInFor, parentGroupedStmt
1248		if grouped {
1249			m.write(closeParenBytes)
1250		}
1251	case *js.ArrowFunc:
1252		parentGroupedStmt := m.groupedStmt
1253		m.groupedStmt = false
1254		m.minifyArrowFunc(expr)
1255		m.groupedStmt = parentGroupedStmt
1256	case *js.MethodDecl:
1257		parentGroupedStmt := m.groupedStmt
1258		m.groupedStmt = false
1259		m.minifyMethodDecl(expr) // only happens in object literal
1260		m.groupedStmt = parentGroupedStmt
1261	case *js.ClassDecl:
1262		if m.expectExpr == expectExprStmt {
1263			m.write(notBytes)
1264		}
1265		parentInFor, parentGroupedStmt := m.inFor, m.groupedStmt
1266		m.inFor, m.groupedStmt = false, false
1267		m.minifyClassDecl(expr)
1268		m.inFor, m.groupedStmt = parentInFor, parentGroupedStmt
1269	case *js.CommaExpr:
1270		for i, item := range expr.List {
1271			if i != 0 {
1272				m.write(commaBytes)
1273			}
1274			m.minifyExpr(item, js.OpAssign)
1275		}
1276	}
1277}