github.com/evanw/esbuild@v0.21.4/internal/js_printer/js_printer.go (about) 1 package js_printer 2 3 import ( 4 "bytes" 5 "fmt" 6 "math" 7 "strconv" 8 "strings" 9 "unicode/utf8" 10 11 "github.com/evanw/esbuild/internal/ast" 12 "github.com/evanw/esbuild/internal/compat" 13 "github.com/evanw/esbuild/internal/config" 14 "github.com/evanw/esbuild/internal/helpers" 15 "github.com/evanw/esbuild/internal/js_ast" 16 "github.com/evanw/esbuild/internal/logger" 17 "github.com/evanw/esbuild/internal/renamer" 18 "github.com/evanw/esbuild/internal/sourcemap" 19 ) 20 21 var positiveInfinity = math.Inf(1) 22 var negativeInfinity = math.Inf(-1) 23 24 const hexChars = "0123456789ABCDEF" 25 const firstASCII = 0x20 26 const lastASCII = 0x7E 27 const firstHighSurrogate = 0xD800 28 const lastHighSurrogate = 0xDBFF 29 const firstLowSurrogate = 0xDC00 30 const lastLowSurrogate = 0xDFFF 31 32 func QuoteIdentifier(js []byte, name string, unsupportedFeatures compat.JSFeature) []byte { 33 isASCII := false 34 asciiStart := 0 35 for i, c := range name { 36 if c >= firstASCII && c <= lastASCII { 37 // Fast path: a run of ASCII characters 38 if !isASCII { 39 isASCII = true 40 asciiStart = i 41 } 42 } else { 43 // Slow path: escape non-ACSII characters 44 if isASCII { 45 js = append(js, name[asciiStart:i]...) 46 isASCII = false 47 } 48 if c <= 0xFFFF { 49 js = append(js, '\\', 'u', hexChars[c>>12], hexChars[(c>>8)&15], hexChars[(c>>4)&15], hexChars[c&15]) 50 } else if !unsupportedFeatures.Has(compat.UnicodeEscapes) { 51 js = append(js, fmt.Sprintf("\\u{%X}", c)...) 52 } else { 53 panic("Internal error: Cannot encode identifier: Unicode escapes are unsupported") 54 } 55 } 56 } 57 if isASCII { 58 // Print one final run of ASCII characters 59 js = append(js, name[asciiStart:]...) 60 } 61 return js 62 } 63 64 func (p *printer) printUnquotedUTF16(text []uint16, quote rune, flags printQuotedFlags) { 65 temp := make([]byte, utf8.UTFMax) 66 js := p.js 67 i := 0 68 n := len(text) 69 70 // Only compute the line length if necessary 71 var startLineLength int 72 wrapLongLines := false 73 if p.options.LineLimit > 0 && (flags&printQuotedNoWrap) == 0 { 74 startLineLength = p.currentLineLength() 75 if startLineLength > p.options.LineLimit { 76 startLineLength = p.options.LineLimit 77 } 78 wrapLongLines = true 79 } 80 81 for i < n { 82 // Wrap long lines that are over the limit using escaped newlines 83 if wrapLongLines && startLineLength+i >= p.options.LineLimit { 84 js = append(js, "\\\n"...) 85 startLineLength -= p.options.LineLimit 86 } 87 88 c := text[i] 89 i++ 90 91 switch c { 92 // Special-case the null character since it may mess with code written in C 93 // that treats null characters as the end of the string. 94 case '\x00': 95 // We don't want "\x001" to be written as "\01" 96 if i < n && text[i] >= '0' && text[i] <= '9' { 97 js = append(js, "\\x00"...) 98 } else { 99 js = append(js, "\\0"...) 100 } 101 102 // Special-case the bell character since it may cause dumping this file to 103 // the terminal to make a sound, which is undesirable. Note that we can't 104 // use an octal literal to print this shorter since octal literals are not 105 // allowed in strict mode (or in template strings). 106 case '\x07': 107 js = append(js, "\\x07"...) 108 109 case '\b': 110 js = append(js, "\\b"...) 111 112 case '\f': 113 js = append(js, "\\f"...) 114 115 case '\n': 116 if quote == '`' { 117 startLineLength = -i // Printing a real newline resets the line length 118 js = append(js, '\n') 119 } else { 120 js = append(js, "\\n"...) 121 } 122 123 case '\r': 124 js = append(js, "\\r"...) 125 126 case '\v': 127 js = append(js, "\\v"...) 128 129 case '\x1B': 130 js = append(js, "\\x1B"...) 131 132 case '\\': 133 js = append(js, "\\\\"...) 134 135 case '/': 136 // Avoid generating the sequence "</script" in JS code 137 if !p.options.UnsupportedFeatures.Has(compat.InlineScript) && i >= 2 && text[i-2] == '<' && i+6 <= len(text) { 138 script := "script" 139 matches := true 140 for j := 0; j < 6; j++ { 141 a := text[i+j] 142 b := uint16(script[j]) 143 if a >= 'A' && a <= 'Z' { 144 a += 'a' - 'A' 145 } 146 if a != b { 147 matches = false 148 break 149 } 150 } 151 if matches { 152 js = append(js, '\\') 153 } 154 } 155 js = append(js, '/') 156 157 case '\'': 158 if quote == '\'' { 159 js = append(js, '\\') 160 } 161 js = append(js, '\'') 162 163 case '"': 164 if quote == '"' { 165 js = append(js, '\\') 166 } 167 js = append(js, '"') 168 169 case '`': 170 if quote == '`' { 171 js = append(js, '\\') 172 } 173 js = append(js, '`') 174 175 case '$': 176 if quote == '`' && i < n && text[i] == '{' { 177 js = append(js, '\\') 178 } 179 js = append(js, '$') 180 181 case '\u2028': 182 js = append(js, "\\u2028"...) 183 184 case '\u2029': 185 js = append(js, "\\u2029"...) 186 187 case '\uFEFF': 188 js = append(js, "\\uFEFF"...) 189 190 default: 191 switch { 192 // Common case: just append a single byte 193 case c <= lastASCII: 194 js = append(js, byte(c)) 195 196 // Is this a high surrogate? 197 case c >= firstHighSurrogate && c <= lastHighSurrogate: 198 // Is there a next character? 199 if i < n { 200 c2 := text[i] 201 202 // Is it a low surrogate? 203 if c2 >= firstLowSurrogate && c2 <= lastLowSurrogate { 204 r := (rune(c) << 10) + rune(c2) + (0x10000 - (firstHighSurrogate << 10) - firstLowSurrogate) 205 i++ 206 207 // Escape this character if UTF-8 isn't allowed 208 if p.options.ASCIIOnly { 209 if !p.options.UnsupportedFeatures.Has(compat.UnicodeEscapes) { 210 js = append(js, fmt.Sprintf("\\u{%X}", r)...) 211 } else { 212 js = append(js, 213 '\\', 'u', hexChars[c>>12], hexChars[(c>>8)&15], hexChars[(c>>4)&15], hexChars[c&15], 214 '\\', 'u', hexChars[c2>>12], hexChars[(c2>>8)&15], hexChars[(c2>>4)&15], hexChars[c2&15], 215 ) 216 } 217 continue 218 } 219 220 // Otherwise, encode to UTF-8 221 width := utf8.EncodeRune(temp, r) 222 js = append(js, temp[:width]...) 223 continue 224 } 225 } 226 227 // Write an unpaired high surrogate 228 js = append(js, '\\', 'u', hexChars[c>>12], hexChars[(c>>8)&15], hexChars[(c>>4)&15], hexChars[c&15]) 229 230 // Is this an unpaired low surrogate or four-digit hex escape? 231 case (c >= firstLowSurrogate && c <= lastLowSurrogate) || (p.options.ASCIIOnly && c > 0xFF): 232 js = append(js, '\\', 'u', hexChars[c>>12], hexChars[(c>>8)&15], hexChars[(c>>4)&15], hexChars[c&15]) 233 234 // Can this be a two-digit hex escape? 235 case p.options.ASCIIOnly: 236 js = append(js, '\\', 'x', hexChars[c>>4], hexChars[c&15]) 237 238 // Otherwise, just encode to UTF-8 239 default: 240 width := utf8.EncodeRune(temp, rune(c)) 241 js = append(js, temp[:width]...) 242 } 243 } 244 } 245 246 p.js = js 247 } 248 249 // JSX tag syntax doesn't support character escapes so non-ASCII identifiers 250 // must be printed as UTF-8 even when the charset is set to ASCII. 251 func (p *printer) printJSXTag(tagOrNil js_ast.Expr) { 252 switch e := tagOrNil.Data.(type) { 253 case *js_ast.EString: 254 p.addSourceMapping(tagOrNil.Loc) 255 p.print(helpers.UTF16ToString(e.Value)) 256 257 case *js_ast.EIdentifier: 258 name := p.renamer.NameForSymbol(e.Ref) 259 p.addSourceMappingForName(tagOrNil.Loc, name, e.Ref) 260 p.print(name) 261 262 case *js_ast.EDot: 263 p.printJSXTag(e.Target) 264 p.print(".") 265 p.addSourceMapping(e.NameLoc) 266 p.print(e.Name) 267 268 default: 269 if tagOrNil.Data != nil { 270 p.printExpr(tagOrNil, js_ast.LLowest, 0) 271 } 272 } 273 } 274 275 type printer struct { 276 symbols ast.SymbolMap 277 astHelpers js_ast.HelperContext 278 renamer renamer.Renamer 279 importRecords []ast.ImportRecord 280 callTarget js_ast.E 281 exprComments map[logger.Loc][]string 282 printedExprComments map[logger.Loc]bool 283 hasLegalComment map[string]struct{} 284 extractedLegalComments []string 285 js []byte 286 jsonMetadataImports []string 287 binaryExprStack []binaryExprVisitor 288 options Options 289 builder sourcemap.ChunkBuilder 290 printNextIndentAsSpace bool 291 292 stmtStart int 293 exportDefaultStart int 294 arrowExprStart int 295 forOfInitStart int 296 297 withNesting int 298 prevOpEnd int 299 needSpaceBeforeDot int 300 prevRegExpEnd int 301 noLeadingNewlineHere int 302 oldLineStart int 303 oldLineEnd int 304 intToBytesBuffer [64]byte 305 needsSemicolon bool 306 wasLazyExport bool 307 prevOp js_ast.OpCode 308 moduleType js_ast.ModuleType 309 } 310 311 func (p *printer) print(text string) { 312 p.js = append(p.js, text...) 313 } 314 315 // This is the same as "print(string(bytes))" without any unnecessary temporary 316 // allocations 317 func (p *printer) printBytes(bytes []byte) { 318 p.js = append(p.js, bytes...) 319 } 320 321 type printQuotedFlags uint8 322 323 const ( 324 printQuotedAllowBacktick printQuotedFlags = 1 << iota 325 printQuotedNoWrap 326 ) 327 328 func (p *printer) printQuotedUTF8(text string, flags printQuotedFlags) { 329 p.printQuotedUTF16(helpers.StringToUTF16(text), flags) 330 } 331 332 func (p *printer) addSourceMapping(loc logger.Loc) { 333 if p.options.AddSourceMappings { 334 p.builder.AddSourceMapping(loc, "", p.js) 335 } 336 } 337 338 func (p *printer) addSourceMappingForName(loc logger.Loc, name string, ref ast.Ref) { 339 if p.options.AddSourceMappings { 340 if originalName := p.symbols.Get(ast.FollowSymbols(p.symbols, ref)).OriginalName; originalName != name { 341 p.builder.AddSourceMapping(loc, originalName, p.js) 342 } else { 343 p.builder.AddSourceMapping(loc, "", p.js) 344 } 345 } 346 } 347 348 func (p *printer) printIndent() { 349 if p.options.MinifyWhitespace { 350 return 351 } 352 353 if p.printNextIndentAsSpace { 354 p.print(" ") 355 p.printNextIndentAsSpace = false 356 return 357 } 358 359 indent := p.options.Indent 360 if p.options.LineLimit > 0 && indent*2 >= p.options.LineLimit { 361 indent = p.options.LineLimit / 2 362 } 363 for i := 0; i < indent; i++ { 364 p.print(" ") 365 } 366 } 367 368 func (p *printer) mangledPropName(ref ast.Ref) string { 369 ref = ast.FollowSymbols(p.symbols, ref) 370 if name, ok := p.options.MangledProps[ref]; ok { 371 return name 372 } 373 return p.renamer.NameForSymbol(ref) 374 } 375 376 func (p *printer) tryToGetImportedEnumValue(target js_ast.Expr, name string) (js_ast.TSEnumValue, bool) { 377 if id, ok := target.Data.(*js_ast.EImportIdentifier); ok { 378 ref := ast.FollowSymbols(p.symbols, id.Ref) 379 if symbol := p.symbols.Get(ref); symbol.Kind == ast.SymbolTSEnum { 380 if enum, ok := p.options.TSEnums[ref]; ok { 381 value, ok := enum[name] 382 return value, ok 383 } 384 } 385 } 386 return js_ast.TSEnumValue{}, false 387 } 388 389 func (p *printer) tryToGetImportedEnumValueUTF16(target js_ast.Expr, name []uint16) (js_ast.TSEnumValue, string, bool) { 390 if id, ok := target.Data.(*js_ast.EImportIdentifier); ok { 391 ref := ast.FollowSymbols(p.symbols, id.Ref) 392 if symbol := p.symbols.Get(ref); symbol.Kind == ast.SymbolTSEnum { 393 if enum, ok := p.options.TSEnums[ref]; ok { 394 name := helpers.UTF16ToString(name) 395 value, ok := enum[name] 396 return value, name, ok 397 } 398 } 399 } 400 return js_ast.TSEnumValue{}, "", false 401 } 402 403 func (p *printer) printClauseAlias(loc logger.Loc, alias string) { 404 if js_ast.IsIdentifier(alias) { 405 p.printSpaceBeforeIdentifier() 406 p.addSourceMapping(loc) 407 p.printIdentifier(alias) 408 } else { 409 p.addSourceMapping(loc) 410 p.printQuotedUTF8(alias, 0) 411 } 412 } 413 414 // Note: The functions below check whether something can be printed as an 415 // identifier or if it needs to be quoted (e.g. "x.y" vs. "x['y']") using the 416 // ES5 identifier validity test to maximize cross-platform portability. Even 417 // though newer JavaScript environments can handle more Unicode characters, 418 // there isn't a published document that says which Unicode versions are 419 // supported by which browsers. Even if a character is considered valid in the 420 // latest version of Unicode, we don't know if the browser we're targeting 421 // contains an older version of Unicode or not. So for safety, we quote 422 // anything that isn't guaranteed to be compatible with ES5, the oldest 423 // JavaScript language target that we support. 424 425 func CanEscapeIdentifier(name string, UnsupportedFeatures compat.JSFeature, asciiOnly bool) bool { 426 return js_ast.IsIdentifierES5AndESNext(name) && (!asciiOnly || 427 !UnsupportedFeatures.Has(compat.UnicodeEscapes) || 428 !helpers.ContainsNonBMPCodePoint(name)) 429 } 430 431 func (p *printer) canPrintIdentifier(name string) bool { 432 return js_ast.IsIdentifierES5AndESNext(name) && (!p.options.ASCIIOnly || 433 !p.options.UnsupportedFeatures.Has(compat.UnicodeEscapes) || 434 !helpers.ContainsNonBMPCodePoint(name)) 435 } 436 437 func (p *printer) canPrintIdentifierUTF16(name []uint16) bool { 438 return js_ast.IsIdentifierES5AndESNextUTF16(name) && (!p.options.ASCIIOnly || 439 !p.options.UnsupportedFeatures.Has(compat.UnicodeEscapes) || 440 !helpers.ContainsNonBMPCodePointUTF16(name)) 441 } 442 443 func (p *printer) printIdentifier(name string) { 444 if p.options.ASCIIOnly { 445 p.js = QuoteIdentifier(p.js, name, p.options.UnsupportedFeatures) 446 } else { 447 p.print(name) 448 } 449 } 450 451 // This is the same as "printIdentifier(StringToUTF16(bytes))" without any 452 // unnecessary temporary allocations 453 func (p *printer) printIdentifierUTF16(name []uint16) { 454 var temp [utf8.UTFMax]byte 455 n := len(name) 456 457 for i := 0; i < n; i++ { 458 c := rune(name[i]) 459 460 if c >= firstHighSurrogate && c <= lastHighSurrogate && i+1 < n { 461 if c2 := rune(name[i+1]); c2 >= firstLowSurrogate && c2 <= lastLowSurrogate { 462 c = (c << 10) + c2 + (0x10000 - (firstHighSurrogate << 10) - firstLowSurrogate) 463 i++ 464 } 465 } 466 467 if p.options.ASCIIOnly && c > lastASCII { 468 if c <= 0xFFFF { 469 p.js = append(p.js, '\\', 'u', hexChars[c>>12], hexChars[(c>>8)&15], hexChars[(c>>4)&15], hexChars[c&15]) 470 } else if !p.options.UnsupportedFeatures.Has(compat.UnicodeEscapes) { 471 p.js = append(p.js, fmt.Sprintf("\\u{%X}", c)...) 472 } else { 473 panic("Internal error: Cannot encode identifier: Unicode escapes are unsupported") 474 } 475 continue 476 } 477 478 width := utf8.EncodeRune(temp[:], c) 479 p.js = append(p.js, temp[:width]...) 480 } 481 } 482 483 func (p *printer) printNumber(value float64, level js_ast.L) { 484 absValue := math.Abs(value) 485 486 if value != value { 487 p.printSpaceBeforeIdentifier() 488 if p.withNesting != 0 { 489 // "with (x) NaN" really means "x.NaN" so avoid identifiers when "with" is present 490 wrap := level >= js_ast.LMultiply 491 if wrap { 492 p.print("(") 493 } 494 if p.options.MinifyWhitespace { 495 p.print("0/0") 496 } else { 497 p.print("0 / 0") 498 } 499 if wrap { 500 p.print(")") 501 } 502 } else { 503 p.print("NaN") 504 } 505 } else if value == positiveInfinity || value == negativeInfinity { 506 // "with (x) Infinity" really means "x.Infinity" so avoid identifiers when "with" is present 507 wrap := ((p.options.MinifySyntax || p.withNesting != 0) && level >= js_ast.LMultiply) || 508 (value == negativeInfinity && level >= js_ast.LPrefix) 509 if wrap { 510 p.print("(") 511 } 512 if value == negativeInfinity { 513 p.printSpaceBeforeOperator(js_ast.UnOpNeg) 514 p.print("-") 515 } else { 516 p.printSpaceBeforeIdentifier() 517 } 518 if !p.options.MinifySyntax && p.withNesting == 0 { 519 p.print("Infinity") 520 } else if p.options.MinifyWhitespace { 521 p.print("1/0") 522 } else { 523 p.print("1 / 0") 524 } 525 if wrap { 526 p.print(")") 527 } 528 } else { 529 if !math.Signbit(value) { 530 p.printSpaceBeforeIdentifier() 531 p.printNonNegativeFloat(absValue) 532 } else if level >= js_ast.LPrefix { 533 // Expressions such as "(-1).toString" need to wrap negative numbers. 534 // Instead of testing for "value < 0" we test for "signbit(value)" and 535 // "!isNaN(value)" because we need this to be true for "-0" and "-0 < 0" 536 // is false. 537 p.print("(-") 538 p.printNonNegativeFloat(absValue) 539 p.print(")") 540 } else { 541 p.printSpaceBeforeOperator(js_ast.UnOpNeg) 542 p.print("-") 543 p.printNonNegativeFloat(absValue) 544 } 545 } 546 } 547 548 func (p *printer) willPrintExprCommentsAtLoc(loc logger.Loc) bool { 549 return !p.options.MinifyWhitespace && p.exprComments[loc] != nil && !p.printedExprComments[loc] 550 } 551 552 func (p *printer) willPrintExprCommentsForAnyOf(exprs []js_ast.Expr) bool { 553 for _, expr := range exprs { 554 if p.willPrintExprCommentsAtLoc(expr.Loc) { 555 return true 556 } 557 } 558 return false 559 } 560 561 func (p *printer) printBinding(binding js_ast.Binding) { 562 switch b := binding.Data.(type) { 563 case *js_ast.BMissing: 564 p.addSourceMapping(binding.Loc) 565 566 case *js_ast.BIdentifier: 567 name := p.renamer.NameForSymbol(b.Ref) 568 p.printSpaceBeforeIdentifier() 569 p.addSourceMappingForName(binding.Loc, name, b.Ref) 570 p.printIdentifier(name) 571 572 case *js_ast.BArray: 573 isMultiLine := (len(b.Items) > 0 && !b.IsSingleLine) || p.willPrintExprCommentsAtLoc(b.CloseBracketLoc) 574 if !p.options.MinifyWhitespace && !isMultiLine { 575 for _, item := range b.Items { 576 if p.willPrintExprCommentsAtLoc(item.Loc) { 577 isMultiLine = true 578 break 579 } 580 } 581 } 582 p.addSourceMapping(binding.Loc) 583 p.print("[") 584 if len(b.Items) > 0 || isMultiLine { 585 if isMultiLine { 586 p.options.Indent++ 587 } 588 589 for i, item := range b.Items { 590 if i != 0 { 591 p.print(",") 592 } 593 if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() { 594 if isMultiLine { 595 p.printNewline() 596 p.printIndent() 597 } else if i != 0 { 598 p.printSpace() 599 } 600 } 601 p.printExprCommentsAtLoc(item.Loc) 602 if b.HasSpread && i+1 == len(b.Items) { 603 p.addSourceMapping(item.Loc) 604 p.print("...") 605 p.printExprCommentsAtLoc(item.Binding.Loc) 606 } 607 p.printBinding(item.Binding) 608 609 if item.DefaultValueOrNil.Data != nil { 610 p.printSpace() 611 p.print("=") 612 p.printSpace() 613 p.printExprWithoutLeadingNewline(item.DefaultValueOrNil, js_ast.LComma, 0) 614 } 615 616 // Make sure there's a comma after trailing missing items 617 if _, ok := item.Binding.Data.(*js_ast.BMissing); ok && i == len(b.Items)-1 { 618 p.print(",") 619 } 620 } 621 622 if isMultiLine { 623 p.printNewline() 624 p.printExprCommentsAfterCloseTokenAtLoc(b.CloseBracketLoc) 625 p.options.Indent-- 626 p.printIndent() 627 } 628 } 629 p.addSourceMapping(b.CloseBracketLoc) 630 p.print("]") 631 632 case *js_ast.BObject: 633 isMultiLine := (len(b.Properties) > 0 && !b.IsSingleLine) || p.willPrintExprCommentsAtLoc(b.CloseBraceLoc) 634 if !p.options.MinifyWhitespace && !isMultiLine { 635 for _, property := range b.Properties { 636 if p.willPrintExprCommentsAtLoc(property.Loc) { 637 isMultiLine = true 638 break 639 } 640 } 641 } 642 p.addSourceMapping(binding.Loc) 643 p.print("{") 644 if len(b.Properties) > 0 || isMultiLine { 645 if isMultiLine { 646 p.options.Indent++ 647 } 648 649 for i, property := range b.Properties { 650 if i != 0 { 651 p.print(",") 652 } 653 if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() { 654 if isMultiLine { 655 p.printNewline() 656 p.printIndent() 657 } else { 658 p.printSpace() 659 } 660 } 661 662 p.printExprCommentsAtLoc(property.Loc) 663 664 if property.IsSpread { 665 p.addSourceMapping(property.Loc) 666 p.print("...") 667 p.printExprCommentsAtLoc(property.Value.Loc) 668 } else { 669 if property.IsComputed { 670 p.addSourceMapping(property.Loc) 671 isMultiLine := p.willPrintExprCommentsAtLoc(property.Key.Loc) || p.willPrintExprCommentsAtLoc(property.CloseBracketLoc) 672 p.print("[") 673 if isMultiLine { 674 p.printNewline() 675 p.options.Indent++ 676 p.printIndent() 677 } 678 p.printExpr(property.Key, js_ast.LComma, 0) 679 if isMultiLine { 680 p.printNewline() 681 p.printExprCommentsAfterCloseTokenAtLoc(property.CloseBracketLoc) 682 p.options.Indent-- 683 p.printIndent() 684 } 685 if property.CloseBracketLoc.Start > property.Loc.Start { 686 p.addSourceMapping(property.CloseBracketLoc) 687 } 688 p.print("]:") 689 p.printSpace() 690 p.printBinding(property.Value) 691 692 if property.DefaultValueOrNil.Data != nil { 693 p.printSpace() 694 p.print("=") 695 p.printSpace() 696 p.printExprWithoutLeadingNewline(property.DefaultValueOrNil, js_ast.LComma, 0) 697 } 698 continue 699 } 700 701 if str, ok := property.Key.Data.(*js_ast.EString); ok && !property.PreferQuotedKey && p.canPrintIdentifierUTF16(str.Value) { 702 // Use a shorthand property if the names are the same 703 if id, ok := property.Value.Data.(*js_ast.BIdentifier); ok && 704 !p.willPrintExprCommentsAtLoc(property.Value.Loc) && 705 helpers.UTF16EqualsString(str.Value, p.renamer.NameForSymbol(id.Ref)) { 706 if p.options.AddSourceMappings { 707 p.addSourceMappingForName(property.Key.Loc, helpers.UTF16ToString(str.Value), id.Ref) 708 } 709 p.printIdentifierUTF16(str.Value) 710 if property.DefaultValueOrNil.Data != nil { 711 p.printSpace() 712 p.print("=") 713 p.printSpace() 714 p.printExprWithoutLeadingNewline(property.DefaultValueOrNil, js_ast.LComma, 0) 715 } 716 continue 717 } 718 719 p.addSourceMapping(property.Key.Loc) 720 p.printIdentifierUTF16(str.Value) 721 } else if mangled, ok := property.Key.Data.(*js_ast.ENameOfSymbol); ok { 722 if name := p.mangledPropName(mangled.Ref); p.canPrintIdentifier(name) { 723 p.addSourceMappingForName(property.Key.Loc, name, mangled.Ref) 724 p.printIdentifier(name) 725 726 // Use a shorthand property if the names are the same 727 if id, ok := property.Value.Data.(*js_ast.BIdentifier); ok && 728 !p.willPrintExprCommentsAtLoc(property.Value.Loc) && 729 name == p.renamer.NameForSymbol(id.Ref) { 730 if property.DefaultValueOrNil.Data != nil { 731 p.printSpace() 732 p.print("=") 733 p.printSpace() 734 p.printExprWithoutLeadingNewline(property.DefaultValueOrNil, js_ast.LComma, 0) 735 } 736 continue 737 } 738 } else { 739 p.addSourceMapping(property.Key.Loc) 740 p.printQuotedUTF8(name, 0) 741 } 742 } else { 743 p.printExpr(property.Key, js_ast.LLowest, 0) 744 } 745 746 p.print(":") 747 p.printSpace() 748 } 749 p.printBinding(property.Value) 750 751 if property.DefaultValueOrNil.Data != nil { 752 p.printSpace() 753 p.print("=") 754 p.printSpace() 755 p.printExprWithoutLeadingNewline(property.DefaultValueOrNil, js_ast.LComma, 0) 756 } 757 } 758 759 if isMultiLine { 760 p.printNewline() 761 p.printExprCommentsAfterCloseTokenAtLoc(b.CloseBraceLoc) 762 p.options.Indent-- 763 p.printIndent() 764 } else { 765 // This block is only reached if len(b.Properties) > 0 766 p.printSpace() 767 } 768 } 769 p.addSourceMapping(b.CloseBraceLoc) 770 p.print("}") 771 772 default: 773 panic(fmt.Sprintf("Unexpected binding of type %T", binding.Data)) 774 } 775 } 776 777 func (p *printer) printSpace() { 778 if !p.options.MinifyWhitespace { 779 p.print(" ") 780 } 781 } 782 783 func (p *printer) printNewline() { 784 if !p.options.MinifyWhitespace { 785 p.print("\n") 786 } 787 } 788 789 func (p *printer) currentLineLength() int { 790 js := p.js 791 n := len(js) 792 stop := p.oldLineEnd 793 794 // Update "oldLineStart" to the start of the current line 795 for i := n; i > stop; i-- { 796 if c := js[i-1]; c == '\r' || c == '\n' { 797 p.oldLineStart = i 798 break 799 } 800 } 801 802 p.oldLineEnd = n 803 return n - p.oldLineStart 804 } 805 806 func (p *printer) printNewlinePastLineLimit() bool { 807 if p.currentLineLength() < p.options.LineLimit { 808 return false 809 } 810 p.print("\n") 811 p.printIndent() 812 return true 813 } 814 815 func (p *printer) printSpaceBeforeOperator(next js_ast.OpCode) { 816 if p.prevOpEnd == len(p.js) { 817 prev := p.prevOp 818 819 // "+ + y" => "+ +y" 820 // "+ ++ y" => "+ ++y" 821 // "x + + y" => "x+ +y" 822 // "x ++ + y" => "x+++y" 823 // "x + ++ y" => "x+ ++y" 824 // "-- >" => "-- >" 825 // "< ! --" => "<! --" 826 if ((prev == js_ast.BinOpAdd || prev == js_ast.UnOpPos) && (next == js_ast.BinOpAdd || next == js_ast.UnOpPos || next == js_ast.UnOpPreInc)) || 827 ((prev == js_ast.BinOpSub || prev == js_ast.UnOpNeg) && (next == js_ast.BinOpSub || next == js_ast.UnOpNeg || next == js_ast.UnOpPreDec)) || 828 (prev == js_ast.UnOpPostDec && next == js_ast.BinOpGt) || 829 (prev == js_ast.UnOpNot && next == js_ast.UnOpPreDec && len(p.js) > 1 && p.js[len(p.js)-2] == '<') { 830 p.print(" ") 831 } 832 } 833 } 834 835 func (p *printer) printSemicolonAfterStatement() { 836 if !p.options.MinifyWhitespace { 837 p.print(";\n") 838 } else { 839 p.needsSemicolon = true 840 } 841 } 842 843 func (p *printer) printSemicolonIfNeeded() { 844 if p.needsSemicolon { 845 p.print(";") 846 p.needsSemicolon = false 847 } 848 } 849 850 func (p *printer) printSpaceBeforeIdentifier() { 851 if c, _ := utf8.DecodeLastRune(p.js); js_ast.IsIdentifierContinue(c) || p.prevRegExpEnd == len(p.js) { 852 p.print(" ") 853 } 854 } 855 856 type fnArgsOpts struct { 857 openParenLoc logger.Loc 858 addMappingForOpenParenLoc bool 859 hasRestArg bool 860 isArrow bool 861 } 862 863 func (p *printer) printFnArgs(args []js_ast.Arg, opts fnArgsOpts) { 864 wrap := true 865 866 // Minify "(a) => {}" as "a=>{}" 867 if p.options.MinifyWhitespace && !opts.hasRestArg && opts.isArrow && len(args) == 1 { 868 if _, ok := args[0].Binding.Data.(*js_ast.BIdentifier); ok && args[0].DefaultOrNil.Data == nil { 869 wrap = false 870 } 871 } 872 873 if wrap { 874 if opts.addMappingForOpenParenLoc { 875 p.addSourceMapping(opts.openParenLoc) 876 } 877 p.print("(") 878 } 879 880 for i, arg := range args { 881 if i != 0 { 882 p.print(",") 883 p.printSpace() 884 } 885 p.printDecorators(arg.Decorators, printSpaceAfterDecorator) 886 if opts.hasRestArg && i+1 == len(args) { 887 p.print("...") 888 } 889 p.printBinding(arg.Binding) 890 891 if arg.DefaultOrNil.Data != nil { 892 p.printSpace() 893 p.print("=") 894 p.printSpace() 895 p.printExprWithoutLeadingNewline(arg.DefaultOrNil, js_ast.LComma, 0) 896 } 897 } 898 899 if wrap { 900 p.print(")") 901 } 902 } 903 904 func (p *printer) printFn(fn js_ast.Fn) { 905 p.printFnArgs(fn.Args, fnArgsOpts{hasRestArg: fn.HasRestArg}) 906 p.printSpace() 907 p.printBlock(fn.Body.Loc, fn.Body.Block) 908 } 909 910 type printAfterDecorator uint8 911 912 const ( 913 printNewlineAfterDecorator printAfterDecorator = iota 914 printSpaceAfterDecorator 915 ) 916 917 func (p *printer) printDecorators(decorators []js_ast.Decorator, defaultMode printAfterDecorator) (omitIndentAfter bool) { 918 oldMode := defaultMode 919 920 for _, decorator := range decorators { 921 wrap := false 922 wasCallTarget := false 923 expr := decorator.Value 924 mode := defaultMode 925 if decorator.OmitNewlineAfter { 926 mode = printSpaceAfterDecorator 927 } 928 929 outer: 930 for { 931 isCallTarget := wasCallTarget 932 wasCallTarget = false 933 934 switch e := expr.Data.(type) { 935 case *js_ast.EIdentifier: 936 // "@foo" 937 break outer 938 939 case *js_ast.ECall: 940 // "@foo()" 941 expr = e.Target 942 wasCallTarget = true 943 continue 944 945 case *js_ast.EDot: 946 // "@foo.bar" 947 if p.canPrintIdentifier(e.Name) { 948 expr = e.Target 949 continue 950 } 951 952 // "@foo.\u30FF" => "@(foo['\u30FF'])" 953 break 954 955 case *js_ast.EIndex: 956 if _, ok := e.Index.Data.(*js_ast.EPrivateIdentifier); ok { 957 // "@foo.#bar" 958 expr = e.Target 959 continue 960 } 961 962 // "@(foo[bar])" 963 break 964 965 case *js_ast.EImportIdentifier: 966 ref := ast.FollowSymbols(p.symbols, e.Ref) 967 symbol := p.symbols.Get(ref) 968 969 if symbol.ImportItemStatus == ast.ImportItemMissing { 970 // "@(void 0)" 971 break 972 } 973 974 if symbol.NamespaceAlias != nil && isCallTarget && e.WasOriginallyIdentifier { 975 // "@((0, import_ns.fn)())" 976 break 977 } 978 979 if value := p.options.ConstValues[ref]; value.Kind != js_ast.ConstValueNone { 980 // "@(<inlined constant>)" 981 break 982 } 983 984 // "@foo" 985 // "@import_ns.fn" 986 break outer 987 988 default: 989 // "@(foo + bar)" 990 // "@(() => {})" 991 break 992 } 993 994 wrap = true 995 break outer 996 } 997 998 p.addSourceMapping(decorator.AtLoc) 999 if oldMode == printNewlineAfterDecorator { 1000 p.printIndent() 1001 } 1002 1003 p.print("@") 1004 if wrap { 1005 p.print("(") 1006 } 1007 p.printExpr(decorator.Value, js_ast.LLowest, 0) 1008 if wrap { 1009 p.print(")") 1010 } 1011 1012 switch mode { 1013 case printNewlineAfterDecorator: 1014 p.printNewline() 1015 1016 case printSpaceAfterDecorator: 1017 p.printSpace() 1018 } 1019 oldMode = mode 1020 } 1021 1022 omitIndentAfter = oldMode == printSpaceAfterDecorator 1023 return 1024 } 1025 1026 func (p *printer) printClass(class js_ast.Class) { 1027 if class.ExtendsOrNil.Data != nil { 1028 p.print(" extends") 1029 p.printSpace() 1030 p.printExpr(class.ExtendsOrNil, js_ast.LNew-1, 0) 1031 } 1032 p.printSpace() 1033 1034 p.addSourceMapping(class.BodyLoc) 1035 p.print("{") 1036 p.printNewline() 1037 p.options.Indent++ 1038 1039 for _, item := range class.Properties { 1040 p.printSemicolonIfNeeded() 1041 omitIndent := p.printDecorators(item.Decorators, printNewlineAfterDecorator) 1042 if !omitIndent { 1043 p.printIndent() 1044 } 1045 1046 if item.Kind == js_ast.PropertyClassStaticBlock { 1047 p.addSourceMapping(item.Loc) 1048 p.print("static") 1049 p.printSpace() 1050 p.printBlock(item.ClassStaticBlock.Loc, item.ClassStaticBlock.Block) 1051 p.printNewline() 1052 continue 1053 } 1054 1055 p.printProperty(item) 1056 1057 // Need semicolons after class fields 1058 if item.ValueOrNil.Data == nil { 1059 p.printSemicolonAfterStatement() 1060 } else { 1061 p.printNewline() 1062 } 1063 } 1064 1065 p.needsSemicolon = false 1066 p.printExprCommentsAfterCloseTokenAtLoc(class.CloseBraceLoc) 1067 p.options.Indent-- 1068 p.printIndent() 1069 if class.CloseBraceLoc.Start > class.BodyLoc.Start { 1070 p.addSourceMapping(class.CloseBraceLoc) 1071 } 1072 p.print("}") 1073 } 1074 1075 func (p *printer) printProperty(property js_ast.Property) { 1076 p.printExprCommentsAtLoc(property.Loc) 1077 1078 if property.Kind == js_ast.PropertySpread { 1079 p.addSourceMapping(property.Loc) 1080 p.print("...") 1081 p.printExpr(property.ValueOrNil, js_ast.LComma, 0) 1082 return 1083 } 1084 1085 // Handle key syntax compression for cross-module constant inlining of enums 1086 if p.options.MinifySyntax && property.Flags.Has(js_ast.PropertyIsComputed) { 1087 if dot, ok := property.Key.Data.(*js_ast.EDot); ok { 1088 if value, ok := p.tryToGetImportedEnumValue(dot.Target, dot.Name); ok { 1089 if value.String != nil { 1090 property.Key.Data = &js_ast.EString{Value: value.String} 1091 1092 // Problematic key names must stay computed for correctness 1093 if !helpers.UTF16EqualsString(value.String, "__proto__") && 1094 !helpers.UTF16EqualsString(value.String, "constructor") && 1095 !helpers.UTF16EqualsString(value.String, "prototype") { 1096 property.Flags &= ^js_ast.PropertyIsComputed 1097 } 1098 } else { 1099 property.Key.Data = &js_ast.ENumber{Value: value.Number} 1100 property.Flags &= ^js_ast.PropertyIsComputed 1101 } 1102 } 1103 } 1104 } 1105 1106 if property.Flags.Has(js_ast.PropertyIsStatic) { 1107 p.printSpaceBeforeIdentifier() 1108 p.addSourceMapping(property.Loc) 1109 p.print("static") 1110 p.printSpace() 1111 } 1112 1113 switch property.Kind { 1114 case js_ast.PropertyGetter: 1115 p.printSpaceBeforeIdentifier() 1116 p.addSourceMapping(property.Loc) 1117 p.print("get") 1118 p.printSpace() 1119 1120 case js_ast.PropertySetter: 1121 p.printSpaceBeforeIdentifier() 1122 p.addSourceMapping(property.Loc) 1123 p.print("set") 1124 p.printSpace() 1125 1126 case js_ast.PropertyAutoAccessor: 1127 p.printSpaceBeforeIdentifier() 1128 p.addSourceMapping(property.Loc) 1129 p.print("accessor") 1130 p.printSpace() 1131 } 1132 1133 if fn, ok := property.ValueOrNil.Data.(*js_ast.EFunction); property.Kind.IsMethodDefinition() && ok { 1134 if fn.Fn.IsAsync { 1135 p.printSpaceBeforeIdentifier() 1136 p.addSourceMapping(property.Loc) 1137 p.print("async") 1138 p.printSpace() 1139 } 1140 if fn.Fn.IsGenerator { 1141 p.addSourceMapping(property.Loc) 1142 p.print("*") 1143 } 1144 } 1145 1146 isComputed := property.Flags.Has(js_ast.PropertyIsComputed) 1147 1148 // Automatically print numbers that would cause a syntax error as computed properties 1149 if !isComputed { 1150 if key, ok := property.Key.Data.(*js_ast.ENumber); ok { 1151 if math.Signbit(key.Value) || (key.Value == positiveInfinity && p.options.MinifySyntax) { 1152 // "{ -1: 0 }" must be printed as "{ [-1]: 0 }" 1153 // "{ 1/0: 0 }" must be printed as "{ [1/0]: 0 }" 1154 isComputed = true 1155 } 1156 } 1157 } 1158 1159 if isComputed { 1160 p.addSourceMapping(property.Loc) 1161 isMultiLine := p.willPrintExprCommentsAtLoc(property.Key.Loc) || p.willPrintExprCommentsAtLoc(property.CloseBracketLoc) 1162 p.print("[") 1163 if isMultiLine { 1164 p.printNewline() 1165 p.options.Indent++ 1166 p.printIndent() 1167 } 1168 p.printExpr(property.Key, js_ast.LComma, 0) 1169 if isMultiLine { 1170 p.printNewline() 1171 p.printExprCommentsAfterCloseTokenAtLoc(property.CloseBracketLoc) 1172 p.options.Indent-- 1173 p.printIndent() 1174 } 1175 if property.CloseBracketLoc.Start > property.Loc.Start { 1176 p.addSourceMapping(property.CloseBracketLoc) 1177 } 1178 p.print("]") 1179 1180 if property.ValueOrNil.Data != nil { 1181 if fn, ok := property.ValueOrNil.Data.(*js_ast.EFunction); property.Kind.IsMethodDefinition() && ok { 1182 p.printFn(fn.Fn) 1183 return 1184 } 1185 1186 p.print(":") 1187 p.printSpace() 1188 p.printExprWithoutLeadingNewline(property.ValueOrNil, js_ast.LComma, 0) 1189 } 1190 1191 if property.InitializerOrNil.Data != nil { 1192 p.printSpace() 1193 p.print("=") 1194 p.printSpace() 1195 p.printExprWithoutLeadingNewline(property.InitializerOrNil, js_ast.LComma, 0) 1196 } 1197 return 1198 } 1199 1200 switch key := property.Key.Data.(type) { 1201 case *js_ast.EPrivateIdentifier: 1202 name := p.renamer.NameForSymbol(key.Ref) 1203 p.addSourceMappingForName(property.Key.Loc, name, key.Ref) 1204 p.printIdentifier(name) 1205 1206 case *js_ast.ENameOfSymbol: 1207 if name := p.mangledPropName(key.Ref); p.canPrintIdentifier(name) { 1208 p.printSpaceBeforeIdentifier() 1209 p.addSourceMappingForName(property.Key.Loc, name, key.Ref) 1210 p.printIdentifier(name) 1211 1212 // Use a shorthand property if the names are the same 1213 if !p.options.UnsupportedFeatures.Has(compat.ObjectExtensions) && property.ValueOrNil.Data != nil && !p.willPrintExprCommentsAtLoc(property.ValueOrNil.Loc) { 1214 switch e := property.ValueOrNil.Data.(type) { 1215 case *js_ast.EIdentifier: 1216 if name == p.renamer.NameForSymbol(e.Ref) { 1217 if property.InitializerOrNil.Data != nil { 1218 p.printSpace() 1219 p.print("=") 1220 p.printSpace() 1221 p.printExprWithoutLeadingNewline(property.InitializerOrNil, js_ast.LComma, 0) 1222 } 1223 return 1224 } 1225 1226 case *js_ast.EImportIdentifier: 1227 // Make sure we're not using a property access instead of an identifier 1228 ref := ast.FollowSymbols(p.symbols, e.Ref) 1229 if symbol := p.symbols.Get(ref); symbol.NamespaceAlias == nil && name == p.renamer.NameForSymbol(ref) && 1230 p.options.ConstValues[ref].Kind == js_ast.ConstValueNone { 1231 if property.InitializerOrNil.Data != nil { 1232 p.printSpace() 1233 p.print("=") 1234 p.printSpace() 1235 p.printExprWithoutLeadingNewline(property.InitializerOrNil, js_ast.LComma, 0) 1236 } 1237 return 1238 } 1239 } 1240 } 1241 } else { 1242 p.addSourceMapping(property.Key.Loc) 1243 p.printQuotedUTF8(name, 0) 1244 } 1245 1246 case *js_ast.EString: 1247 if !property.Flags.Has(js_ast.PropertyPreferQuotedKey) && p.canPrintIdentifierUTF16(key.Value) { 1248 p.printSpaceBeforeIdentifier() 1249 1250 // Use a shorthand property if the names are the same 1251 if !p.options.UnsupportedFeatures.Has(compat.ObjectExtensions) && property.ValueOrNil.Data != nil && !p.willPrintExprCommentsAtLoc(property.ValueOrNil.Loc) { 1252 switch e := property.ValueOrNil.Data.(type) { 1253 case *js_ast.EIdentifier: 1254 if canUseShorthandProperty(key.Value, p.renamer.NameForSymbol(e.Ref), property.Flags) { 1255 if p.options.AddSourceMappings { 1256 p.addSourceMappingForName(property.Key.Loc, helpers.UTF16ToString(key.Value), e.Ref) 1257 } 1258 p.printIdentifierUTF16(key.Value) 1259 if property.InitializerOrNil.Data != nil { 1260 p.printSpace() 1261 p.print("=") 1262 p.printSpace() 1263 p.printExprWithoutLeadingNewline(property.InitializerOrNil, js_ast.LComma, 0) 1264 } 1265 return 1266 } 1267 1268 case *js_ast.EImportIdentifier: 1269 // Make sure we're not using a property access instead of an identifier 1270 ref := ast.FollowSymbols(p.symbols, e.Ref) 1271 if symbol := p.symbols.Get(ref); symbol.NamespaceAlias == nil && canUseShorthandProperty(key.Value, p.renamer.NameForSymbol(ref), property.Flags) && 1272 p.options.ConstValues[ref].Kind == js_ast.ConstValueNone { 1273 if p.options.AddSourceMappings { 1274 p.addSourceMappingForName(property.Key.Loc, helpers.UTF16ToString(key.Value), ref) 1275 } 1276 p.printIdentifierUTF16(key.Value) 1277 if property.InitializerOrNil.Data != nil { 1278 p.printSpace() 1279 p.print("=") 1280 p.printSpace() 1281 p.printExprWithoutLeadingNewline(property.InitializerOrNil, js_ast.LComma, 0) 1282 } 1283 return 1284 } 1285 } 1286 } 1287 1288 // The JavaScript specification special-cases the property identifier 1289 // "__proto__" with a colon after it to set the prototype of the object. 1290 // If we keep the identifier but add a colon then we'll cause a behavior 1291 // change because the prototype will now be set. Avoid using an identifier 1292 // by using a computed property with a string instead. For more info see: 1293 // https://tc39.es/ecma262/#sec-runtime-semantics-propertydefinitionevaluation 1294 if property.Flags.Has(js_ast.PropertyWasShorthand) && !p.options.UnsupportedFeatures.Has(compat.ObjectExtensions) && 1295 helpers.UTF16EqualsString(key.Value, "__proto__") { 1296 p.print("[") 1297 p.addSourceMapping(property.Key.Loc) 1298 p.printQuotedUTF16(key.Value, 0) 1299 p.print("]") 1300 break 1301 } 1302 1303 p.addSourceMapping(property.Key.Loc) 1304 p.printIdentifierUTF16(key.Value) 1305 } else { 1306 p.addSourceMapping(property.Key.Loc) 1307 p.printQuotedUTF16(key.Value, 0) 1308 } 1309 1310 default: 1311 p.printExpr(property.Key, js_ast.LLowest, 0) 1312 } 1313 1314 if fn, ok := property.ValueOrNil.Data.(*js_ast.EFunction); property.Kind.IsMethodDefinition() && ok { 1315 p.printFn(fn.Fn) 1316 return 1317 } 1318 1319 if property.ValueOrNil.Data != nil { 1320 p.print(":") 1321 p.printSpace() 1322 p.printExprWithoutLeadingNewline(property.ValueOrNil, js_ast.LComma, 0) 1323 } 1324 1325 if property.InitializerOrNil.Data != nil { 1326 p.printSpace() 1327 p.print("=") 1328 p.printSpace() 1329 p.printExprWithoutLeadingNewline(property.InitializerOrNil, js_ast.LComma, 0) 1330 } 1331 } 1332 1333 func canUseShorthandProperty(key []uint16, name string, flags js_ast.PropertyFlags) bool { 1334 // The JavaScript specification special-cases the property identifier 1335 // "__proto__" with a colon after it to set the prototype of the object. If 1336 // we remove the colon then we'll cause a behavior change because the 1337 // prototype will no longer be set, but we also don't want to add a colon 1338 // if it was omitted. Always use a shorthand property if the property is not 1339 // "__proto__", otherwise try to preserve the original shorthand status. See: 1340 // https://tc39.es/ecma262/#sec-runtime-semantics-propertydefinitionevaluation 1341 if !helpers.UTF16EqualsString(key, name) { 1342 return false 1343 } 1344 return helpers.UTF16EqualsString(key, name) && (name != "__proto__" || flags.Has(js_ast.PropertyWasShorthand)) 1345 } 1346 1347 func (p *printer) printQuotedUTF16(data []uint16, flags printQuotedFlags) { 1348 if p.options.UnsupportedFeatures.Has(compat.TemplateLiteral) { 1349 flags &= ^printQuotedAllowBacktick 1350 } 1351 1352 singleCost := 0 1353 doubleCost := 0 1354 backtickCost := 0 1355 1356 for i, c := range data { 1357 switch c { 1358 case '\n': 1359 if p.options.MinifySyntax { 1360 // The backslash for the newline costs an extra character for old-style 1361 // string literals when compared to a template literal 1362 backtickCost-- 1363 } 1364 case '\'': 1365 singleCost++ 1366 case '"': 1367 doubleCost++ 1368 case '`': 1369 backtickCost++ 1370 case '$': 1371 // "${" sequences need to be escaped in template literals 1372 if i+1 < len(data) && data[i+1] == '{' { 1373 backtickCost++ 1374 } 1375 } 1376 } 1377 1378 c := "\"" 1379 if doubleCost > singleCost { 1380 c = "'" 1381 if singleCost > backtickCost && (flags&printQuotedAllowBacktick) != 0 { 1382 c = "`" 1383 } 1384 } else if doubleCost > backtickCost && (flags&printQuotedAllowBacktick) != 0 { 1385 c = "`" 1386 } 1387 1388 p.print(c) 1389 p.printUnquotedUTF16(data, rune(c[0]), flags) 1390 p.print(c) 1391 } 1392 1393 func (p *printer) printRequireOrImportExpr(importRecordIndex uint32, level js_ast.L, flags printExprFlags, closeParenLoc logger.Loc) { 1394 record := &p.importRecords[importRecordIndex] 1395 1396 if level >= js_ast.LNew || (flags&forbidCall) != 0 { 1397 p.print("(") 1398 defer p.print(")") 1399 level = js_ast.LLowest 1400 } 1401 1402 if !record.SourceIndex.IsValid() { 1403 // External "require()" 1404 if record.Kind != ast.ImportDynamic { 1405 // Wrap this with a call to "__toESM()" if this is a CommonJS file 1406 wrapWithToESM := record.Flags.Has(ast.WrapWithToESM) 1407 if wrapWithToESM { 1408 p.printSpaceBeforeIdentifier() 1409 p.printIdentifier(p.renamer.NameForSymbol(p.options.ToESMRef)) 1410 p.print("(") 1411 } 1412 1413 // Potentially substitute our own "__require" stub for "require" 1414 p.printSpaceBeforeIdentifier() 1415 if record.Flags.Has(ast.CallRuntimeRequire) { 1416 p.printIdentifier(p.renamer.NameForSymbol(p.options.RuntimeRequireRef)) 1417 } else { 1418 p.print("require") 1419 } 1420 1421 isMultiLine := p.willPrintExprCommentsAtLoc(record.Range.Loc) || p.willPrintExprCommentsAtLoc(closeParenLoc) 1422 p.print("(") 1423 if isMultiLine { 1424 p.printNewline() 1425 p.options.Indent++ 1426 p.printIndent() 1427 } 1428 p.printExprCommentsAtLoc(record.Range.Loc) 1429 p.printPath(importRecordIndex, ast.ImportRequire) 1430 if isMultiLine { 1431 p.printNewline() 1432 p.printExprCommentsAfterCloseTokenAtLoc(closeParenLoc) 1433 p.options.Indent-- 1434 p.printIndent() 1435 } 1436 if closeParenLoc.Start > record.Range.Loc.Start { 1437 p.addSourceMapping(closeParenLoc) 1438 } 1439 p.print(")") 1440 1441 // Finish the call to "__toESM()" 1442 if wrapWithToESM { 1443 if p.moduleType.IsESM() { 1444 p.print(",") 1445 p.printSpace() 1446 p.print("1") 1447 } 1448 p.print(")") 1449 } 1450 return 1451 } 1452 1453 // External "import()" 1454 kind := ast.ImportDynamic 1455 if !p.options.UnsupportedFeatures.Has(compat.DynamicImport) { 1456 p.printSpaceBeforeIdentifier() 1457 p.print("import(") 1458 } else { 1459 kind = ast.ImportRequire 1460 p.printSpaceBeforeIdentifier() 1461 p.print("Promise.resolve()") 1462 p.printDotThenPrefix() 1463 defer p.printDotThenSuffix() 1464 1465 // Wrap this with a call to "__toESM()" if this is a CommonJS file 1466 if record.Flags.Has(ast.WrapWithToESM) { 1467 p.printSpaceBeforeIdentifier() 1468 p.printIdentifier(p.renamer.NameForSymbol(p.options.ToESMRef)) 1469 p.print("(") 1470 defer func() { 1471 if p.moduleType.IsESM() { 1472 p.print(",") 1473 p.printSpace() 1474 p.print("1") 1475 } 1476 p.print(")") 1477 }() 1478 } 1479 1480 // Potentially substitute our own "__require" stub for "require" 1481 p.printSpaceBeforeIdentifier() 1482 if record.Flags.Has(ast.CallRuntimeRequire) { 1483 p.printIdentifier(p.renamer.NameForSymbol(p.options.RuntimeRequireRef)) 1484 } else { 1485 p.print("require") 1486 } 1487 1488 p.print("(") 1489 } 1490 isMultiLine := p.willPrintExprCommentsAtLoc(record.Range.Loc) || 1491 p.willPrintExprCommentsAtLoc(closeParenLoc) || 1492 (record.AssertOrWith != nil && 1493 !p.options.UnsupportedFeatures.Has(compat.DynamicImport) && 1494 (!p.options.UnsupportedFeatures.Has(compat.ImportAssertions) || 1495 !p.options.UnsupportedFeatures.Has(compat.ImportAttributes)) && 1496 p.willPrintExprCommentsAtLoc(record.AssertOrWith.OuterOpenBraceLoc)) 1497 if isMultiLine { 1498 p.printNewline() 1499 p.options.Indent++ 1500 p.printIndent() 1501 } 1502 p.printExprCommentsAtLoc(record.Range.Loc) 1503 p.printPath(importRecordIndex, kind) 1504 if !p.options.UnsupportedFeatures.Has(compat.DynamicImport) { 1505 p.printImportCallAssertOrWith(record.AssertOrWith, isMultiLine) 1506 } 1507 if isMultiLine { 1508 p.printNewline() 1509 p.printExprCommentsAfterCloseTokenAtLoc(closeParenLoc) 1510 p.options.Indent-- 1511 p.printIndent() 1512 } 1513 if closeParenLoc.Start > record.Range.Loc.Start { 1514 p.addSourceMapping(closeParenLoc) 1515 } 1516 p.print(")") 1517 return 1518 } 1519 1520 meta := p.options.RequireOrImportMetaForSource(record.SourceIndex.GetIndex()) 1521 1522 // Don't need the namespace object if the result is unused anyway 1523 if (flags & exprResultIsUnused) != 0 { 1524 meta.ExportsRef = ast.InvalidRef 1525 } 1526 1527 // Internal "import()" of async ESM 1528 if record.Kind == ast.ImportDynamic && meta.IsWrapperAsync { 1529 p.printSpaceBeforeIdentifier() 1530 p.printIdentifier(p.renamer.NameForSymbol(meta.WrapperRef)) 1531 p.print("()") 1532 if meta.ExportsRef != ast.InvalidRef { 1533 p.printDotThenPrefix() 1534 p.printSpaceBeforeIdentifier() 1535 p.printIdentifier(p.renamer.NameForSymbol(meta.ExportsRef)) 1536 p.printDotThenSuffix() 1537 } 1538 return 1539 } 1540 1541 // Internal "require()" or "import()" 1542 if record.Kind == ast.ImportDynamic { 1543 p.printSpaceBeforeIdentifier() 1544 p.print("Promise.resolve()") 1545 level = p.printDotThenPrefix() 1546 defer p.printDotThenSuffix() 1547 } 1548 1549 // Make sure the comma operator is properly wrapped 1550 if meta.ExportsRef != ast.InvalidRef && level >= js_ast.LComma { 1551 p.print("(") 1552 defer p.print(")") 1553 } 1554 1555 // Wrap this with a call to "__toESM()" if this is a CommonJS file 1556 wrapWithToESM := record.Flags.Has(ast.WrapWithToESM) 1557 if wrapWithToESM { 1558 p.printSpaceBeforeIdentifier() 1559 p.printIdentifier(p.renamer.NameForSymbol(p.options.ToESMRef)) 1560 p.print("(") 1561 } 1562 1563 // Call the wrapper 1564 p.printSpaceBeforeIdentifier() 1565 p.printIdentifier(p.renamer.NameForSymbol(meta.WrapperRef)) 1566 p.print("()") 1567 1568 // Return the namespace object if this is an ESM file 1569 if meta.ExportsRef != ast.InvalidRef { 1570 p.print(",") 1571 p.printSpace() 1572 1573 // Wrap this with a call to "__toCommonJS()" if this is an ESM file 1574 wrapWithTpCJS := record.Flags.Has(ast.WrapWithToCJS) 1575 if wrapWithTpCJS { 1576 p.printIdentifier(p.renamer.NameForSymbol(p.options.ToCommonJSRef)) 1577 p.print("(") 1578 } 1579 p.printIdentifier(p.renamer.NameForSymbol(meta.ExportsRef)) 1580 if wrapWithTpCJS { 1581 p.print(")") 1582 } 1583 } 1584 1585 // Finish the call to "__toESM()" 1586 if wrapWithToESM { 1587 if p.moduleType.IsESM() { 1588 p.print(",") 1589 p.printSpace() 1590 p.print("1") 1591 } 1592 p.print(")") 1593 } 1594 } 1595 1596 func (p *printer) printDotThenPrefix() js_ast.L { 1597 if p.options.UnsupportedFeatures.Has(compat.Arrow) { 1598 p.print(".then(function()") 1599 p.printSpace() 1600 p.print("{") 1601 p.printNewline() 1602 p.options.Indent++ 1603 p.printIndent() 1604 p.print("return") 1605 p.printSpace() 1606 return js_ast.LLowest 1607 } else { 1608 p.print(".then(()") 1609 p.printSpace() 1610 p.print("=>") 1611 p.printSpace() 1612 return js_ast.LComma 1613 } 1614 } 1615 1616 func (p *printer) printDotThenSuffix() { 1617 if p.options.UnsupportedFeatures.Has(compat.Arrow) { 1618 if !p.options.MinifyWhitespace { 1619 p.print(";") 1620 } 1621 p.printNewline() 1622 p.options.Indent-- 1623 p.printIndent() 1624 p.print("})") 1625 } else { 1626 p.print(")") 1627 } 1628 } 1629 1630 func (p *printer) printUndefined(loc logger.Loc, level js_ast.L) { 1631 if level >= js_ast.LPrefix { 1632 p.addSourceMapping(loc) 1633 p.print("(void 0)") 1634 } else { 1635 p.printSpaceBeforeIdentifier() 1636 p.addSourceMapping(loc) 1637 p.print("void 0") 1638 } 1639 } 1640 1641 // Call this before printing an expression to see if it turned out to be empty. 1642 // We use this to do inlining of empty functions at print time. It can't happen 1643 // during parse time because a) parse time only has two passes and we only know 1644 // if a function can be inlined at the end of the second pass (due to is-mutated 1645 // analysis) and b) we want to enable cross-module inlining of empty functions 1646 // which has to happen after linking. 1647 // 1648 // This function returns "nil" to indicate that the expression should be removed 1649 // completely. 1650 // 1651 // This function doesn't need to search everywhere inside the entire expression 1652 // for calls to inline. Calls are automatically inlined when printed. However, 1653 // the printer replaces the call with "undefined" since the result may still 1654 // be needed by the caller. If the caller knows that it doesn't need the result, 1655 // it should call this function first instead so we don't print "undefined". 1656 // 1657 // This is a separate function instead of trying to work this logic into the 1658 // printer because it's too late to eliminate the expression entirely when we're 1659 // in the printer. We may have already printed the leading indent, for example. 1660 func (p *printer) simplifyUnusedExpr(expr js_ast.Expr) js_ast.Expr { 1661 switch e := expr.Data.(type) { 1662 case *js_ast.EBinary: 1663 // Calls to be inlined may be hidden inside a comma operator chain 1664 if e.Op == js_ast.BinOpComma { 1665 left := p.simplifyUnusedExpr(e.Left) 1666 right := p.simplifyUnusedExpr(e.Right) 1667 if left.Data != e.Left.Data || right.Data != e.Right.Data { 1668 return js_ast.JoinWithComma(left, right) 1669 } 1670 } 1671 1672 case *js_ast.ECall: 1673 var symbolFlags ast.SymbolFlags 1674 switch target := e.Target.Data.(type) { 1675 case *js_ast.EIdentifier: 1676 symbolFlags = p.symbols.Get(target.Ref).Flags 1677 case *js_ast.EImportIdentifier: 1678 ref := ast.FollowSymbols(p.symbols, target.Ref) 1679 symbolFlags = p.symbols.Get(ref).Flags 1680 } 1681 1682 // Replace non-mutated empty functions with their arguments at print time 1683 if (symbolFlags & (ast.IsEmptyFunction | ast.CouldPotentiallyBeMutated)) == ast.IsEmptyFunction { 1684 var replacement js_ast.Expr 1685 for _, arg := range e.Args { 1686 if _, ok := arg.Data.(*js_ast.ESpread); ok { 1687 arg.Data = &js_ast.EArray{Items: []js_ast.Expr{arg}, IsSingleLine: true} 1688 } 1689 replacement = js_ast.JoinWithComma(replacement, p.astHelpers.SimplifyUnusedExpr(p.simplifyUnusedExpr(arg), p.options.UnsupportedFeatures)) 1690 } 1691 return replacement // Don't add "undefined" here because the result isn't used 1692 } 1693 1694 // Inline non-mutated identity functions at print time 1695 if (symbolFlags&(ast.IsIdentityFunction|ast.CouldPotentiallyBeMutated)) == ast.IsIdentityFunction && len(e.Args) == 1 { 1696 arg := e.Args[0] 1697 if _, ok := arg.Data.(*js_ast.ESpread); !ok { 1698 return p.astHelpers.SimplifyUnusedExpr(p.simplifyUnusedExpr(arg), p.options.UnsupportedFeatures) 1699 } 1700 } 1701 } 1702 1703 return expr 1704 } 1705 1706 // This assumes the original expression was some form of indirect value, such 1707 // as a value returned from a function call or the result of a comma operator. 1708 // In this case, there is no special behavior with the "delete" operator or 1709 // with function calls. If we substitute this indirect value for another value 1710 // due to inlining, we have to make sure we don't accidentally introduce special 1711 // behavior. 1712 func (p *printer) guardAgainstBehaviorChangeDueToSubstitution(expr js_ast.Expr, flags printExprFlags) js_ast.Expr { 1713 wrap := false 1714 1715 if (flags & isDeleteTarget) != 0 { 1716 // "delete id(x)" must not become "delete x" 1717 // "delete (empty(), x)" must not become "delete x" 1718 if binary, ok := expr.Data.(*js_ast.EBinary); !ok || binary.Op != js_ast.BinOpComma { 1719 wrap = true 1720 } 1721 } else if (flags & isCallTargetOrTemplateTag) != 0 { 1722 // "id(x.y)()" must not become "x.y()" 1723 // "id(x.y)``" must not become "x.y``" 1724 // "(empty(), x.y)()" must not become "x.y()" 1725 // "(empty(), eval)()" must not become "eval()" 1726 switch expr.Data.(type) { 1727 case *js_ast.EDot, *js_ast.EIndex: 1728 wrap = true 1729 case *js_ast.EIdentifier: 1730 if p.isUnboundEvalIdentifier(expr) { 1731 wrap = true 1732 } 1733 } 1734 } 1735 1736 if wrap { 1737 expr.Data = &js_ast.EBinary{ 1738 Op: js_ast.BinOpComma, 1739 Left: js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ENumber{Value: 0}}, 1740 Right: expr, 1741 } 1742 } 1743 1744 return expr 1745 } 1746 1747 // Constant folding is already implemented once in the parser. A smaller form 1748 // of constant folding (just for numbers) is implemented here to clean up cross- 1749 // module numeric constants and bitwise operations. This is not an general- 1750 // purpose/optimal approach and never will be. For example, we can't affect 1751 // tree shaking at this stage because it has already happened. 1752 func (p *printer) lateConstantFoldUnaryOrBinaryOrIfExpr(expr js_ast.Expr) js_ast.Expr { 1753 switch e := expr.Data.(type) { 1754 case *js_ast.EImportIdentifier: 1755 ref := ast.FollowSymbols(p.symbols, e.Ref) 1756 if value := p.options.ConstValues[ref]; value.Kind != js_ast.ConstValueNone { 1757 return js_ast.ConstValueToExpr(expr.Loc, value) 1758 } 1759 1760 case *js_ast.EDot: 1761 if value, ok := p.tryToGetImportedEnumValue(e.Target, e.Name); ok { 1762 var inlinedValue js_ast.Expr 1763 if value.String != nil { 1764 inlinedValue = js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EString{Value: value.String}} 1765 } else { 1766 inlinedValue = js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ENumber{Value: value.Number}} 1767 } 1768 1769 if strings.Contains(e.Name, "*/") { 1770 // Don't wrap with a comment 1771 return inlinedValue 1772 } 1773 1774 // Wrap with a comment 1775 return js_ast.Expr{Loc: inlinedValue.Loc, Data: &js_ast.EInlinedEnum{ 1776 Value: inlinedValue, 1777 Comment: e.Name, 1778 }} 1779 } 1780 1781 case *js_ast.EUnary: 1782 value := p.lateConstantFoldUnaryOrBinaryOrIfExpr(e.Value) 1783 1784 // Only fold again if something chained 1785 if value.Data != e.Value.Data { 1786 // Only fold certain operations (just like the parser) 1787 if v, ok := js_ast.ToNumberWithoutSideEffects(value.Data); ok { 1788 switch e.Op { 1789 case js_ast.UnOpPos: 1790 return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ENumber{Value: v}} 1791 1792 case js_ast.UnOpNeg: 1793 return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ENumber{Value: -v}} 1794 1795 case js_ast.UnOpCpl: 1796 return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ENumber{Value: float64(^js_ast.ToInt32(v))}} 1797 } 1798 } 1799 1800 // Don't mutate the original AST 1801 expr.Data = &js_ast.EUnary{Op: e.Op, Value: value} 1802 } 1803 1804 case *js_ast.EBinary: 1805 left := p.lateConstantFoldUnaryOrBinaryOrIfExpr(e.Left) 1806 right := p.lateConstantFoldUnaryOrBinaryOrIfExpr(e.Right) 1807 1808 // Only fold again if something changed 1809 if left.Data != e.Left.Data || right.Data != e.Right.Data { 1810 binary := &js_ast.EBinary{Op: e.Op, Left: left, Right: right} 1811 1812 // Only fold certain operations (just like the parser) 1813 if js_ast.ShouldFoldBinaryOperatorWhenMinifying(binary) { 1814 if result := js_ast.FoldBinaryOperator(expr.Loc, binary); result.Data != nil { 1815 return result 1816 } 1817 } 1818 1819 // Don't mutate the original AST 1820 expr.Data = binary 1821 } 1822 1823 case *js_ast.EIf: 1824 test := p.lateConstantFoldUnaryOrBinaryOrIfExpr(e.Test) 1825 1826 // Only fold again if something changed 1827 if test.Data != e.Test.Data { 1828 if boolean, sideEffects, ok := js_ast.ToBooleanWithSideEffects(test.Data); ok && sideEffects == js_ast.NoSideEffects { 1829 if boolean { 1830 return p.lateConstantFoldUnaryOrBinaryOrIfExpr(e.Yes) 1831 } else { 1832 return p.lateConstantFoldUnaryOrBinaryOrIfExpr(e.No) 1833 } 1834 } 1835 1836 // Don't mutate the original AST 1837 expr.Data = &js_ast.EIf{Test: test, Yes: e.Yes, No: e.No} 1838 } 1839 } 1840 1841 return expr 1842 } 1843 1844 func (p *printer) isUnboundIdentifier(expr js_ast.Expr) bool { 1845 id, ok := expr.Data.(*js_ast.EIdentifier) 1846 return ok && p.symbols.Get(ast.FollowSymbols(p.symbols, id.Ref)).Kind == ast.SymbolUnbound 1847 } 1848 1849 func (p *printer) isIdentifierOrNumericConstantOrPropertyAccess(expr js_ast.Expr) bool { 1850 switch e := expr.Data.(type) { 1851 case *js_ast.EIdentifier, *js_ast.EDot, *js_ast.EIndex: 1852 return true 1853 case *js_ast.ENumber: 1854 return math.IsInf(e.Value, 1) || math.IsNaN(e.Value) 1855 } 1856 return false 1857 } 1858 1859 type exprStartFlags uint8 1860 1861 const ( 1862 stmtStartFlag exprStartFlags = 1 << iota 1863 exportDefaultStartFlag 1864 arrowExprStartFlag 1865 forOfInitStartFlag 1866 ) 1867 1868 func (p *printer) saveExprStartFlags() (flags exprStartFlags) { 1869 n := len(p.js) 1870 if p.stmtStart == n { 1871 flags |= stmtStartFlag 1872 } 1873 if p.exportDefaultStart == n { 1874 flags |= exportDefaultStartFlag 1875 } 1876 if p.arrowExprStart == n { 1877 flags |= arrowExprStartFlag 1878 } 1879 if p.forOfInitStart == n { 1880 flags |= forOfInitStartFlag 1881 } 1882 return 1883 } 1884 1885 func (p *printer) restoreExprStartFlags(flags exprStartFlags) { 1886 if flags != 0 { 1887 n := len(p.js) 1888 if (flags & stmtStartFlag) != 0 { 1889 p.stmtStart = n 1890 } 1891 if (flags & exportDefaultStartFlag) != 0 { 1892 p.exportDefaultStart = n 1893 } 1894 if (flags & arrowExprStartFlag) != 0 { 1895 p.arrowExprStart = n 1896 } 1897 if (flags & forOfInitStartFlag) != 0 { 1898 p.forOfInitStart = n 1899 } 1900 } 1901 } 1902 1903 // Print any stored comments that are associated with this location 1904 func (p *printer) printExprCommentsAtLoc(loc logger.Loc) { 1905 if p.options.MinifyWhitespace { 1906 return 1907 } 1908 if comments := p.exprComments[loc]; comments != nil && !p.printedExprComments[loc] { 1909 flags := p.saveExprStartFlags() 1910 1911 // We must never generate a newline before certain expressions. For example, 1912 // generating a newline before the expression in a "return" statement will 1913 // cause a semicolon to be inserted, which would change the code's behavior. 1914 if p.noLeadingNewlineHere == len(p.js) { 1915 for _, comment := range comments { 1916 if strings.HasPrefix(comment, "//") { 1917 p.print("/*") 1918 p.print(comment[2:]) 1919 if strings.HasPrefix(comment, "// ") { 1920 p.print(" ") 1921 } 1922 p.print("*/") 1923 } else { 1924 p.print(strings.Join(strings.Split(comment, "\n"), "")) 1925 } 1926 p.printSpace() 1927 } 1928 } else { 1929 for _, comment := range comments { 1930 p.printIndentedComment(comment) 1931 p.printIndent() 1932 } 1933 } 1934 1935 // Mark these comments as printed so we don't print them again 1936 p.printedExprComments[loc] = true 1937 1938 p.restoreExprStartFlags(flags) 1939 } 1940 } 1941 1942 func (p *printer) printExprCommentsAfterCloseTokenAtLoc(loc logger.Loc) { 1943 if comments := p.exprComments[loc]; comments != nil && !p.printedExprComments[loc] { 1944 flags := p.saveExprStartFlags() 1945 1946 for _, comment := range comments { 1947 p.printIndent() 1948 p.printIndentedComment(comment) 1949 } 1950 1951 // Mark these comments as printed so we don't print them again 1952 p.printedExprComments[loc] = true 1953 1954 p.restoreExprStartFlags(flags) 1955 } 1956 } 1957 1958 func (p *printer) printExprWithoutLeadingNewline(expr js_ast.Expr, level js_ast.L, flags printExprFlags) { 1959 if !p.options.MinifyWhitespace && p.willPrintExprCommentsAtLoc(expr.Loc) { 1960 p.print("(") 1961 p.printNewline() 1962 p.options.Indent++ 1963 p.printIndent() 1964 p.printExpr(expr, level, flags) 1965 p.printNewline() 1966 p.options.Indent-- 1967 p.printIndent() 1968 p.print(")") 1969 return 1970 } 1971 1972 p.noLeadingNewlineHere = len(p.js) 1973 p.printExpr(expr, level, flags) 1974 } 1975 1976 type printExprFlags uint16 1977 1978 const ( 1979 forbidCall printExprFlags = 1 << iota 1980 forbidIn 1981 hasNonOptionalChainParent 1982 exprResultIsUnused 1983 didAlreadySimplifyUnusedExprs 1984 isFollowedByOf 1985 isInsideForAwait 1986 isDeleteTarget 1987 isCallTargetOrTemplateTag 1988 isPropertyAccessTarget 1989 parentWasUnaryOrBinaryOrIfTest 1990 ) 1991 1992 func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFlags) { 1993 // If syntax compression is enabled, do a pre-pass over unary and binary 1994 // operators to inline bitwise operations of cross-module inlined constants. 1995 // This makes the output a little tighter if people construct bit masks in 1996 // other files. This is not a general-purpose constant folding pass. In 1997 // particular, it has no effect on tree shaking because that pass has already 1998 // been run. 1999 // 2000 // This sets a flag to avoid doing this when the parent is a unary or binary 2001 // operator so that we don't trigger O(n^2) behavior when traversing over a 2002 // large expression tree. 2003 if p.options.MinifySyntax && (flags&parentWasUnaryOrBinaryOrIfTest) == 0 { 2004 switch expr.Data.(type) { 2005 case *js_ast.EUnary, *js_ast.EBinary, *js_ast.EIf: 2006 expr = p.lateConstantFoldUnaryOrBinaryOrIfExpr(expr) 2007 } 2008 } 2009 2010 p.printExprCommentsAtLoc(expr.Loc) 2011 2012 switch e := expr.Data.(type) { 2013 case *js_ast.EMissing: 2014 p.addSourceMapping(expr.Loc) 2015 2016 case *js_ast.EAnnotation: 2017 p.printExpr(e.Value, level, flags) 2018 2019 case *js_ast.EUndefined: 2020 p.printUndefined(expr.Loc, level) 2021 2022 case *js_ast.ESuper: 2023 p.printSpaceBeforeIdentifier() 2024 p.addSourceMapping(expr.Loc) 2025 p.print("super") 2026 2027 case *js_ast.ENull: 2028 p.printSpaceBeforeIdentifier() 2029 p.addSourceMapping(expr.Loc) 2030 p.print("null") 2031 2032 case *js_ast.EThis: 2033 p.printSpaceBeforeIdentifier() 2034 p.addSourceMapping(expr.Loc) 2035 p.print("this") 2036 2037 case *js_ast.ESpread: 2038 p.addSourceMapping(expr.Loc) 2039 p.print("...") 2040 p.printExpr(e.Value, js_ast.LComma, 0) 2041 2042 case *js_ast.ENewTarget: 2043 p.printSpaceBeforeIdentifier() 2044 p.addSourceMapping(expr.Loc) 2045 p.print("new.target") 2046 2047 case *js_ast.EImportMeta: 2048 p.printSpaceBeforeIdentifier() 2049 p.addSourceMapping(expr.Loc) 2050 p.print("import.meta") 2051 2052 case *js_ast.ENameOfSymbol: 2053 name := p.mangledPropName(e.Ref) 2054 p.addSourceMappingForName(expr.Loc, name, e.Ref) 2055 2056 if !p.options.MinifyWhitespace && e.HasPropertyKeyComment { 2057 p.print("/* @__KEY__ */ ") 2058 } 2059 2060 p.printQuotedUTF8(name, printQuotedAllowBacktick) 2061 2062 case *js_ast.EJSXElement: 2063 // Start the opening tag 2064 p.addSourceMapping(expr.Loc) 2065 p.print("<") 2066 p.printJSXTag(e.TagOrNil) 2067 if !e.IsTagSingleLine { 2068 p.options.Indent++ 2069 } 2070 2071 // Print the attributes 2072 for _, property := range e.Properties { 2073 if e.IsTagSingleLine { 2074 p.printSpace() 2075 } else { 2076 p.printNewline() 2077 p.printIndent() 2078 } 2079 2080 if property.Kind == js_ast.PropertySpread { 2081 if p.willPrintExprCommentsAtLoc(property.Loc) { 2082 p.print("{") 2083 p.printNewline() 2084 p.options.Indent++ 2085 p.printIndent() 2086 p.printExprCommentsAtLoc(property.Loc) 2087 p.print("...") 2088 p.printExpr(property.ValueOrNil, js_ast.LComma, 0) 2089 p.printNewline() 2090 p.options.Indent-- 2091 p.printIndent() 2092 p.print("}") 2093 } else { 2094 p.print("{...") 2095 p.printExpr(property.ValueOrNil, js_ast.LComma, 0) 2096 p.print("}") 2097 } 2098 continue 2099 } 2100 2101 p.printSpaceBeforeIdentifier() 2102 if mangled, ok := property.Key.Data.(*js_ast.ENameOfSymbol); ok { 2103 name := p.mangledPropName(mangled.Ref) 2104 p.addSourceMappingForName(property.Key.Loc, name, mangled.Ref) 2105 p.printIdentifier(name) 2106 } else if str, ok := property.Key.Data.(*js_ast.EString); ok { 2107 p.addSourceMapping(property.Key.Loc) 2108 p.print(helpers.UTF16ToString(str.Value)) 2109 } else { 2110 p.print("{...{") 2111 p.printSpace() 2112 p.print("[") 2113 p.printExpr(property.Key, js_ast.LComma, 0) 2114 p.print("]:") 2115 p.printSpace() 2116 p.printExpr(property.ValueOrNil, js_ast.LComma, 0) 2117 p.printSpace() 2118 p.print("}}") 2119 continue 2120 } 2121 2122 isMultiLine := p.willPrintExprCommentsAtLoc(property.ValueOrNil.Loc) 2123 2124 if property.Flags.Has(js_ast.PropertyWasShorthand) { 2125 // Implicit "true" value 2126 if boolean, ok := property.ValueOrNil.Data.(*js_ast.EBoolean); ok && boolean.Value { 2127 continue 2128 } 2129 2130 // JSX element as JSX attribute value 2131 if _, ok := property.ValueOrNil.Data.(*js_ast.EJSXElement); ok { 2132 p.print("=") 2133 p.printExpr(property.ValueOrNil, js_ast.LLowest, 0) 2134 continue 2135 } 2136 } 2137 2138 // Special-case raw text 2139 if text, ok := property.ValueOrNil.Data.(*js_ast.EJSXText); ok { 2140 p.print("=") 2141 p.addSourceMapping(property.ValueOrNil.Loc) 2142 p.print(text.Raw) 2143 continue 2144 } 2145 2146 // Generic JS value 2147 p.print("={") 2148 if isMultiLine { 2149 p.printNewline() 2150 p.options.Indent++ 2151 p.printIndent() 2152 } 2153 p.printExpr(property.ValueOrNil, js_ast.LComma, 0) 2154 if isMultiLine { 2155 p.printNewline() 2156 p.options.Indent-- 2157 p.printIndent() 2158 } 2159 p.print("}") 2160 } 2161 2162 // End the opening tag 2163 if !e.IsTagSingleLine { 2164 p.options.Indent-- 2165 if len(e.Properties) > 0 { 2166 p.printNewline() 2167 p.printIndent() 2168 } 2169 } 2170 if e.TagOrNil.Data != nil && len(e.NullableChildren) == 0 { 2171 if e.IsTagSingleLine || len(e.Properties) == 0 { 2172 p.printSpace() 2173 } 2174 p.addSourceMapping(e.CloseLoc) 2175 p.print("/>") 2176 break 2177 } 2178 p.print(">") 2179 2180 // Print the children 2181 for _, childOrNil := range e.NullableChildren { 2182 if _, ok := childOrNil.Data.(*js_ast.EJSXElement); ok { 2183 p.printExpr(childOrNil, js_ast.LLowest, 0) 2184 } else if text, ok := childOrNil.Data.(*js_ast.EJSXText); ok { 2185 p.addSourceMapping(childOrNil.Loc) 2186 p.print(text.Raw) 2187 } else if childOrNil.Data != nil { 2188 isMultiLine := p.willPrintExprCommentsAtLoc(childOrNil.Loc) 2189 p.print("{") 2190 if isMultiLine { 2191 p.printNewline() 2192 p.options.Indent++ 2193 p.printIndent() 2194 } 2195 p.printExpr(childOrNil, js_ast.LComma, 0) 2196 if isMultiLine { 2197 p.printNewline() 2198 p.options.Indent-- 2199 p.printIndent() 2200 } 2201 p.print("}") 2202 } else { 2203 p.print("{") 2204 if p.willPrintExprCommentsAtLoc(childOrNil.Loc) { 2205 // Note: Some people use these comments for AST transformations 2206 p.printNewline() 2207 p.options.Indent++ 2208 p.printExprCommentsAfterCloseTokenAtLoc(childOrNil.Loc) 2209 p.options.Indent-- 2210 p.printIndent() 2211 } 2212 p.print("}") 2213 } 2214 } 2215 2216 // Print the closing tag 2217 p.addSourceMapping(e.CloseLoc) 2218 p.print("</") 2219 p.printJSXTag(e.TagOrNil) 2220 p.print(">") 2221 2222 case *js_ast.ENew: 2223 wrap := level >= js_ast.LCall 2224 2225 hasPureComment := !p.options.MinifyWhitespace && e.CanBeUnwrappedIfUnused 2226 if hasPureComment && level >= js_ast.LPostfix { 2227 wrap = true 2228 } 2229 2230 if wrap { 2231 p.print("(") 2232 } 2233 2234 if hasPureComment { 2235 p.addSourceMapping(expr.Loc) 2236 p.print("/* @__PURE__ */ ") 2237 } 2238 2239 p.printSpaceBeforeIdentifier() 2240 p.addSourceMapping(expr.Loc) 2241 p.print("new") 2242 p.printSpace() 2243 p.printExpr(e.Target, js_ast.LNew, forbidCall) 2244 2245 // Omit the "()" when minifying, but only when safe to do so 2246 isMultiLine := !p.options.MinifyWhitespace && ((e.IsMultiLine && len(e.Args) > 0) || 2247 p.willPrintExprCommentsForAnyOf(e.Args) || 2248 p.willPrintExprCommentsAtLoc(e.CloseParenLoc)) 2249 if !p.options.MinifyWhitespace || len(e.Args) > 0 || level >= js_ast.LPostfix || isMultiLine { 2250 needsNewline := true 2251 p.print("(") 2252 if isMultiLine { 2253 p.options.Indent++ 2254 } 2255 for i, arg := range e.Args { 2256 if i != 0 { 2257 p.print(",") 2258 } 2259 if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() { 2260 if isMultiLine { 2261 if needsNewline { 2262 p.printNewline() 2263 } 2264 p.printIndent() 2265 } else if i != 0 { 2266 p.printSpace() 2267 } 2268 } 2269 p.printExpr(arg, js_ast.LComma, 0) 2270 needsNewline = true 2271 } 2272 if isMultiLine { 2273 if needsNewline || p.willPrintExprCommentsAtLoc(e.CloseParenLoc) { 2274 p.printNewline() 2275 } 2276 p.printExprCommentsAfterCloseTokenAtLoc(e.CloseParenLoc) 2277 p.options.Indent-- 2278 p.printIndent() 2279 } 2280 if e.CloseParenLoc.Start > expr.Loc.Start { 2281 p.addSourceMapping(e.CloseParenLoc) 2282 } 2283 p.print(")") 2284 } 2285 2286 if wrap { 2287 p.print(")") 2288 } 2289 2290 case *js_ast.ECall: 2291 if p.options.MinifySyntax { 2292 var symbolFlags ast.SymbolFlags 2293 switch target := e.Target.Data.(type) { 2294 case *js_ast.EIdentifier: 2295 symbolFlags = p.symbols.Get(target.Ref).Flags 2296 case *js_ast.EImportIdentifier: 2297 ref := ast.FollowSymbols(p.symbols, target.Ref) 2298 symbolFlags = p.symbols.Get(ref).Flags 2299 } 2300 2301 // Replace non-mutated empty functions with their arguments at print time 2302 if (symbolFlags & (ast.IsEmptyFunction | ast.CouldPotentiallyBeMutated)) == ast.IsEmptyFunction { 2303 var replacement js_ast.Expr 2304 for _, arg := range e.Args { 2305 if _, ok := arg.Data.(*js_ast.ESpread); ok { 2306 arg.Data = &js_ast.EArray{Items: []js_ast.Expr{arg}, IsSingleLine: true} 2307 } 2308 replacement = js_ast.JoinWithComma(replacement, p.astHelpers.SimplifyUnusedExpr(arg, p.options.UnsupportedFeatures)) 2309 } 2310 if replacement.Data == nil || (flags&exprResultIsUnused) == 0 { 2311 replacement = js_ast.JoinWithComma(replacement, js_ast.Expr{Loc: expr.Loc, Data: js_ast.EUndefinedShared}) 2312 } 2313 p.printExpr(p.guardAgainstBehaviorChangeDueToSubstitution(replacement, flags), level, flags) 2314 break 2315 } 2316 2317 // Inline non-mutated identity functions at print time 2318 if (symbolFlags&(ast.IsIdentityFunction|ast.CouldPotentiallyBeMutated)) == ast.IsIdentityFunction && len(e.Args) == 1 { 2319 arg := e.Args[0] 2320 if _, ok := arg.Data.(*js_ast.ESpread); !ok { 2321 if (flags & exprResultIsUnused) != 0 { 2322 arg = p.astHelpers.SimplifyUnusedExpr(arg, p.options.UnsupportedFeatures) 2323 } 2324 p.printExpr(p.guardAgainstBehaviorChangeDueToSubstitution(arg, flags), level, flags) 2325 break 2326 } 2327 } 2328 2329 // Inline IIFEs that return expressions at print time 2330 if len(e.Args) == 0 { 2331 // Note: Do not inline async arrow functions as they are not IIFEs. In 2332 // particular, they are not necessarily invoked immediately, and any 2333 // exceptions involved in their evaluation will be swallowed without 2334 // bubbling up to the surrounding context. 2335 if arrow, ok := e.Target.Data.(*js_ast.EArrow); ok && len(arrow.Args) == 0 && !arrow.IsAsync { 2336 stmts := arrow.Body.Block.Stmts 2337 2338 // "(() => {})()" => "void 0" 2339 if len(stmts) == 0 { 2340 value := js_ast.Expr{Loc: expr.Loc, Data: js_ast.EUndefinedShared} 2341 p.printExpr(p.guardAgainstBehaviorChangeDueToSubstitution(value, flags), level, flags) 2342 break 2343 } 2344 2345 // "(() => 123)()" => "123" 2346 if len(stmts) == 1 { 2347 if stmt, ok := stmts[0].Data.(*js_ast.SReturn); ok { 2348 value := stmt.ValueOrNil 2349 if value.Data == nil { 2350 value.Data = js_ast.EUndefinedShared 2351 } 2352 p.printExpr(p.guardAgainstBehaviorChangeDueToSubstitution(value, flags), level, flags) 2353 break 2354 } 2355 } 2356 } 2357 } 2358 } 2359 2360 wrap := level >= js_ast.LNew || (flags&forbidCall) != 0 2361 var targetFlags printExprFlags 2362 if e.OptionalChain == js_ast.OptionalChainNone { 2363 targetFlags = hasNonOptionalChainParent 2364 } else if (flags & hasNonOptionalChainParent) != 0 { 2365 wrap = true 2366 } 2367 2368 hasPureComment := !p.options.MinifyWhitespace && e.CanBeUnwrappedIfUnused 2369 if hasPureComment && level >= js_ast.LPostfix { 2370 wrap = true 2371 } 2372 2373 if wrap { 2374 p.print("(") 2375 } 2376 2377 if hasPureComment { 2378 flags := p.saveExprStartFlags() 2379 p.addSourceMapping(expr.Loc) 2380 p.print("/* @__PURE__ */ ") 2381 p.restoreExprStartFlags(flags) 2382 } 2383 2384 // We don't ever want to accidentally generate a direct eval expression here 2385 p.callTarget = e.Target.Data 2386 if (e.Kind != js_ast.DirectEval && p.isUnboundEvalIdentifier(e.Target) && e.OptionalChain == js_ast.OptionalChainNone) || 2387 (e.Kind != js_ast.TargetWasOriginallyPropertyAccess && js_ast.IsPropertyAccess(e.Target)) { 2388 p.print("(0,") 2389 p.printSpace() 2390 p.printExpr(e.Target, js_ast.LPostfix, isCallTargetOrTemplateTag) 2391 p.print(")") 2392 } else { 2393 p.printExpr(e.Target, js_ast.LPostfix, isCallTargetOrTemplateTag|targetFlags) 2394 } 2395 2396 if e.OptionalChain == js_ast.OptionalChainStart { 2397 p.print("?.") 2398 } 2399 2400 isMultiLine := !p.options.MinifyWhitespace && ((e.IsMultiLine && len(e.Args) > 0) || 2401 p.willPrintExprCommentsForAnyOf(e.Args) || 2402 p.willPrintExprCommentsAtLoc(e.CloseParenLoc)) 2403 p.print("(") 2404 if isMultiLine { 2405 p.options.Indent++ 2406 } 2407 for i, arg := range e.Args { 2408 if i != 0 { 2409 p.print(",") 2410 } 2411 if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() { 2412 if isMultiLine { 2413 p.printNewline() 2414 p.printIndent() 2415 } else if i != 0 { 2416 p.printSpace() 2417 } 2418 } 2419 p.printExpr(arg, js_ast.LComma, 0) 2420 } 2421 if isMultiLine { 2422 p.printNewline() 2423 p.printExprCommentsAfterCloseTokenAtLoc(e.CloseParenLoc) 2424 p.options.Indent-- 2425 p.printIndent() 2426 } 2427 if e.CloseParenLoc.Start > expr.Loc.Start { 2428 p.addSourceMapping(e.CloseParenLoc) 2429 } 2430 p.print(")") 2431 2432 if wrap { 2433 p.print(")") 2434 } 2435 2436 case *js_ast.ERequireString: 2437 p.addSourceMapping(expr.Loc) 2438 p.printRequireOrImportExpr(e.ImportRecordIndex, level, flags, e.CloseParenLoc) 2439 2440 case *js_ast.ERequireResolveString: 2441 recordLoc := p.importRecords[e.ImportRecordIndex].Range.Loc 2442 isMultiLine := p.willPrintExprCommentsAtLoc(recordLoc) || p.willPrintExprCommentsAtLoc(e.CloseParenLoc) 2443 wrap := level >= js_ast.LNew || (flags&forbidCall) != 0 2444 if wrap { 2445 p.print("(") 2446 } 2447 p.printSpaceBeforeIdentifier() 2448 p.addSourceMapping(expr.Loc) 2449 p.print("require.resolve(") 2450 if isMultiLine { 2451 p.printNewline() 2452 p.options.Indent++ 2453 p.printIndent() 2454 p.printExprCommentsAtLoc(recordLoc) 2455 } 2456 p.printPath(e.ImportRecordIndex, ast.ImportRequireResolve) 2457 if isMultiLine { 2458 p.printNewline() 2459 p.printExprCommentsAfterCloseTokenAtLoc(e.CloseParenLoc) 2460 p.options.Indent-- 2461 p.printIndent() 2462 } 2463 if e.CloseParenLoc.Start > expr.Loc.Start { 2464 p.addSourceMapping(e.CloseParenLoc) 2465 } 2466 p.print(")") 2467 if wrap { 2468 p.print(")") 2469 } 2470 2471 case *js_ast.EImportString: 2472 p.addSourceMapping(expr.Loc) 2473 p.printRequireOrImportExpr(e.ImportRecordIndex, level, flags, e.CloseParenLoc) 2474 2475 case *js_ast.EImportCall: 2476 // Only print the second argument if either import assertions or import attributes are supported 2477 printImportAssertOrWith := e.OptionsOrNil.Data != nil && (!p.options.UnsupportedFeatures.Has(compat.ImportAssertions) || !p.options.UnsupportedFeatures.Has(compat.ImportAttributes)) 2478 isMultiLine := !p.options.MinifyWhitespace && 2479 (p.willPrintExprCommentsAtLoc(e.Expr.Loc) || 2480 (printImportAssertOrWith && p.willPrintExprCommentsAtLoc(e.OptionsOrNil.Loc)) || 2481 p.willPrintExprCommentsAtLoc(e.CloseParenLoc)) 2482 wrap := level >= js_ast.LNew || (flags&forbidCall) != 0 2483 if wrap { 2484 p.print("(") 2485 } 2486 p.printSpaceBeforeIdentifier() 2487 p.addSourceMapping(expr.Loc) 2488 p.print("import(") 2489 if isMultiLine { 2490 p.printNewline() 2491 p.options.Indent++ 2492 p.printIndent() 2493 } 2494 p.printExpr(e.Expr, js_ast.LComma, 0) 2495 2496 if printImportAssertOrWith { 2497 p.print(",") 2498 if isMultiLine { 2499 p.printNewline() 2500 p.printIndent() 2501 } else { 2502 p.printSpace() 2503 } 2504 p.printExpr(e.OptionsOrNil, js_ast.LComma, 0) 2505 } 2506 2507 if isMultiLine { 2508 p.printNewline() 2509 p.printExprCommentsAfterCloseTokenAtLoc(e.CloseParenLoc) 2510 p.options.Indent-- 2511 p.printIndent() 2512 } 2513 p.print(")") 2514 if wrap { 2515 p.print(")") 2516 } 2517 2518 case *js_ast.EDot: 2519 wrap := false 2520 if e.OptionalChain == js_ast.OptionalChainNone { 2521 flags |= hasNonOptionalChainParent 2522 2523 // Inline cross-module TypeScript enum references here 2524 if value, ok := p.tryToGetImportedEnumValue(e.Target, e.Name); ok { 2525 if value.String != nil { 2526 p.printQuotedUTF16(value.String, printQuotedAllowBacktick) 2527 } else { 2528 p.printNumber(value.Number, level) 2529 } 2530 if !p.options.MinifyWhitespace && !p.options.MinifyIdentifiers && !strings.Contains(e.Name, "*/") { 2531 p.print(" /* ") 2532 p.print(e.Name) 2533 p.print(" */") 2534 } 2535 break 2536 } 2537 } else { 2538 if (flags & hasNonOptionalChainParent) != 0 { 2539 wrap = true 2540 p.print("(") 2541 } 2542 flags &= ^hasNonOptionalChainParent 2543 } 2544 p.printExpr(e.Target, js_ast.LPostfix, (flags&(forbidCall|hasNonOptionalChainParent))|isPropertyAccessTarget) 2545 if p.canPrintIdentifier(e.Name) { 2546 if e.OptionalChain != js_ast.OptionalChainStart && p.needSpaceBeforeDot == len(p.js) { 2547 // "1.toString" is a syntax error, so print "1 .toString" instead 2548 p.print(" ") 2549 } 2550 if e.OptionalChain == js_ast.OptionalChainStart { 2551 p.print("?.") 2552 } else { 2553 p.print(".") 2554 } 2555 if p.options.LineLimit > 0 { 2556 p.printNewlinePastLineLimit() 2557 } 2558 p.addSourceMapping(e.NameLoc) 2559 p.printIdentifier(e.Name) 2560 } else { 2561 if e.OptionalChain == js_ast.OptionalChainStart { 2562 p.print("?.") 2563 } 2564 p.print("[") 2565 p.addSourceMapping(e.NameLoc) 2566 p.printQuotedUTF8(e.Name, printQuotedAllowBacktick) 2567 p.print("]") 2568 } 2569 if wrap { 2570 p.print(")") 2571 } 2572 2573 case *js_ast.EIndex: 2574 if e.OptionalChain == js_ast.OptionalChainNone { 2575 flags |= hasNonOptionalChainParent 2576 2577 // Inline cross-module TypeScript enum references here 2578 if index, ok := e.Index.Data.(*js_ast.EString); ok { 2579 if value, name, ok := p.tryToGetImportedEnumValueUTF16(e.Target, index.Value); ok { 2580 if value.String != nil { 2581 p.printQuotedUTF16(value.String, printQuotedAllowBacktick) 2582 } else { 2583 p.printNumber(value.Number, level) 2584 } 2585 if !p.options.MinifyWhitespace && !p.options.MinifyIdentifiers && !strings.Contains(name, "*/") { 2586 p.print(" /* ") 2587 p.print(name) 2588 p.print(" */") 2589 } 2590 break 2591 } 2592 } 2593 } else { 2594 if (flags & hasNonOptionalChainParent) != 0 { 2595 p.print("(") 2596 defer p.print(")") 2597 } 2598 flags &= ^hasNonOptionalChainParent 2599 } 2600 p.printExpr(e.Target, js_ast.LPostfix, (flags&(forbidCall|hasNonOptionalChainParent))|isPropertyAccessTarget) 2601 if e.OptionalChain == js_ast.OptionalChainStart { 2602 p.print("?.") 2603 } 2604 2605 switch index := e.Index.Data.(type) { 2606 case *js_ast.EPrivateIdentifier: 2607 if e.OptionalChain != js_ast.OptionalChainStart { 2608 p.print(".") 2609 } 2610 name := p.renamer.NameForSymbol(index.Ref) 2611 p.addSourceMappingForName(e.Index.Loc, name, index.Ref) 2612 p.printIdentifier(name) 2613 return 2614 2615 case *js_ast.ENameOfSymbol: 2616 if name := p.mangledPropName(index.Ref); p.canPrintIdentifier(name) { 2617 if e.OptionalChain != js_ast.OptionalChainStart { 2618 p.print(".") 2619 } 2620 p.addSourceMappingForName(e.Index.Loc, name, index.Ref) 2621 p.printIdentifier(name) 2622 return 2623 } 2624 2625 case *js_ast.EInlinedEnum: 2626 if p.options.MinifySyntax { 2627 if str, ok := index.Value.Data.(*js_ast.EString); ok && p.canPrintIdentifierUTF16(str.Value) { 2628 if e.OptionalChain != js_ast.OptionalChainStart { 2629 p.print(".") 2630 } 2631 p.addSourceMapping(index.Value.Loc) 2632 p.printIdentifierUTF16(str.Value) 2633 return 2634 } 2635 } 2636 2637 case *js_ast.EDot: 2638 if p.options.MinifySyntax { 2639 if value, ok := p.tryToGetImportedEnumValue(index.Target, index.Name); ok && value.String != nil && p.canPrintIdentifierUTF16(value.String) { 2640 if e.OptionalChain != js_ast.OptionalChainStart { 2641 p.print(".") 2642 } 2643 p.addSourceMapping(e.Index.Loc) 2644 p.printIdentifierUTF16(value.String) 2645 return 2646 } 2647 } 2648 } 2649 2650 isMultiLine := p.willPrintExprCommentsAtLoc(e.Index.Loc) || p.willPrintExprCommentsAtLoc(e.CloseBracketLoc) 2651 p.print("[") 2652 if isMultiLine { 2653 p.printNewline() 2654 p.options.Indent++ 2655 p.printIndent() 2656 } 2657 p.printExpr(e.Index, js_ast.LLowest, 0) 2658 if isMultiLine { 2659 p.printNewline() 2660 p.printExprCommentsAfterCloseTokenAtLoc(e.CloseBracketLoc) 2661 p.options.Indent-- 2662 p.printIndent() 2663 } 2664 if e.CloseBracketLoc.Start > expr.Loc.Start { 2665 p.addSourceMapping(e.CloseBracketLoc) 2666 } 2667 p.print("]") 2668 2669 case *js_ast.EIf: 2670 wrap := level >= js_ast.LConditional 2671 if wrap { 2672 p.print("(") 2673 flags &= ^forbidIn 2674 } 2675 p.printExpr(e.Test, js_ast.LConditional, (flags&forbidIn)|parentWasUnaryOrBinaryOrIfTest) 2676 p.printSpace() 2677 p.print("?") 2678 if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() { 2679 p.printSpace() 2680 } 2681 p.printExprWithoutLeadingNewline(e.Yes, js_ast.LYield, 0) 2682 p.printSpace() 2683 p.print(":") 2684 if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() { 2685 p.printSpace() 2686 } 2687 p.printExprWithoutLeadingNewline(e.No, js_ast.LYield, flags&forbidIn) 2688 if wrap { 2689 p.print(")") 2690 } 2691 2692 case *js_ast.EArrow: 2693 wrap := level >= js_ast.LAssign 2694 2695 if wrap { 2696 p.print("(") 2697 } 2698 if !p.options.MinifyWhitespace && e.HasNoSideEffectsComment { 2699 p.print("/* @__NO_SIDE_EFFECTS__ */ ") 2700 } 2701 if e.IsAsync { 2702 p.addSourceMapping(expr.Loc) 2703 p.printSpaceBeforeIdentifier() 2704 p.print("async") 2705 p.printSpace() 2706 } 2707 2708 p.printFnArgs(e.Args, fnArgsOpts{ 2709 openParenLoc: expr.Loc, 2710 addMappingForOpenParenLoc: !e.IsAsync, 2711 hasRestArg: e.HasRestArg, 2712 isArrow: true, 2713 }) 2714 p.printSpace() 2715 p.print("=>") 2716 p.printSpace() 2717 2718 wasPrinted := false 2719 if len(e.Body.Block.Stmts) == 1 && e.PreferExpr { 2720 if s, ok := e.Body.Block.Stmts[0].Data.(*js_ast.SReturn); ok && s.ValueOrNil.Data != nil { 2721 p.arrowExprStart = len(p.js) 2722 p.printExprWithoutLeadingNewline(s.ValueOrNil, js_ast.LComma, flags&forbidIn) 2723 wasPrinted = true 2724 } 2725 } 2726 if !wasPrinted { 2727 p.printBlock(e.Body.Loc, e.Body.Block) 2728 } 2729 if wrap { 2730 p.print(")") 2731 } 2732 2733 case *js_ast.EFunction: 2734 n := len(p.js) 2735 wrap := p.stmtStart == n || p.exportDefaultStart == n || 2736 ((flags&isPropertyAccessTarget) != 0 && p.options.UnsupportedFeatures.Has(compat.FunctionOrClassPropertyAccess)) 2737 if wrap { 2738 p.print("(") 2739 } 2740 if !p.options.MinifyWhitespace && e.Fn.HasNoSideEffectsComment { 2741 p.print("/* @__NO_SIDE_EFFECTS__ */ ") 2742 } 2743 p.printSpaceBeforeIdentifier() 2744 p.addSourceMapping(expr.Loc) 2745 if e.Fn.IsAsync { 2746 p.print("async ") 2747 } 2748 p.print("function") 2749 if e.Fn.IsGenerator { 2750 p.print("*") 2751 p.printSpace() 2752 } 2753 if e.Fn.Name != nil { 2754 p.printSpaceBeforeIdentifier() 2755 name := p.renamer.NameForSymbol(e.Fn.Name.Ref) 2756 p.addSourceMappingForName(e.Fn.Name.Loc, name, e.Fn.Name.Ref) 2757 p.printIdentifier(name) 2758 } 2759 p.printFn(e.Fn) 2760 if wrap { 2761 p.print(")") 2762 } 2763 2764 case *js_ast.EClass: 2765 n := len(p.js) 2766 wrap := p.stmtStart == n || p.exportDefaultStart == n || 2767 ((flags&isPropertyAccessTarget) != 0 && p.options.UnsupportedFeatures.Has(compat.FunctionOrClassPropertyAccess)) 2768 if wrap { 2769 p.print("(") 2770 } 2771 p.printDecorators(e.Class.Decorators, printSpaceAfterDecorator) 2772 p.printSpaceBeforeIdentifier() 2773 p.addSourceMapping(expr.Loc) 2774 p.print("class") 2775 if e.Class.Name != nil { 2776 p.print(" ") 2777 name := p.renamer.NameForSymbol(e.Class.Name.Ref) 2778 p.addSourceMappingForName(e.Class.Name.Loc, name, e.Class.Name.Ref) 2779 p.printIdentifier(name) 2780 } 2781 p.printClass(e.Class) 2782 if wrap { 2783 p.print(")") 2784 } 2785 2786 case *js_ast.EArray: 2787 isMultiLine := (len(e.Items) > 0 && !e.IsSingleLine) || p.willPrintExprCommentsForAnyOf(e.Items) || p.willPrintExprCommentsAtLoc(e.CloseBracketLoc) 2788 p.addSourceMapping(expr.Loc) 2789 p.print("[") 2790 if len(e.Items) > 0 || isMultiLine { 2791 if isMultiLine { 2792 p.options.Indent++ 2793 } 2794 2795 for i, item := range e.Items { 2796 if i != 0 { 2797 p.print(",") 2798 } 2799 if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() { 2800 if isMultiLine { 2801 p.printNewline() 2802 p.printIndent() 2803 } else if i != 0 { 2804 p.printSpace() 2805 } 2806 } 2807 p.printExpr(item, js_ast.LComma, 0) 2808 2809 // Make sure there's a comma after trailing missing items 2810 _, ok := item.Data.(*js_ast.EMissing) 2811 if ok && i == len(e.Items)-1 { 2812 p.print(",") 2813 } 2814 } 2815 2816 if isMultiLine { 2817 p.printNewline() 2818 p.printExprCommentsAfterCloseTokenAtLoc(e.CloseBracketLoc) 2819 p.options.Indent-- 2820 p.printIndent() 2821 } 2822 } 2823 if e.CloseBracketLoc.Start > expr.Loc.Start { 2824 p.addSourceMapping(e.CloseBracketLoc) 2825 } 2826 p.print("]") 2827 2828 case *js_ast.EObject: 2829 isMultiLine := (len(e.Properties) > 0 && !e.IsSingleLine) || p.willPrintExprCommentsAtLoc(e.CloseBraceLoc) 2830 if !p.options.MinifyWhitespace && !isMultiLine { 2831 for _, property := range e.Properties { 2832 if p.willPrintExprCommentsAtLoc(property.Loc) { 2833 isMultiLine = true 2834 break 2835 } 2836 } 2837 } 2838 n := len(p.js) 2839 wrap := p.stmtStart == n || p.arrowExprStart == n 2840 if wrap { 2841 p.print("(") 2842 } 2843 p.addSourceMapping(expr.Loc) 2844 p.print("{") 2845 if len(e.Properties) > 0 || isMultiLine { 2846 if isMultiLine { 2847 p.options.Indent++ 2848 } 2849 2850 for i, item := range e.Properties { 2851 if i != 0 { 2852 p.print(",") 2853 } 2854 if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() { 2855 if isMultiLine { 2856 p.printNewline() 2857 p.printIndent() 2858 } else { 2859 p.printSpace() 2860 } 2861 } 2862 p.printProperty(item) 2863 } 2864 2865 if isMultiLine { 2866 p.printNewline() 2867 p.printExprCommentsAfterCloseTokenAtLoc(e.CloseBraceLoc) 2868 p.options.Indent-- 2869 p.printIndent() 2870 } else if len(e.Properties) > 0 { 2871 p.printSpace() 2872 } 2873 } 2874 if e.CloseBraceLoc.Start > expr.Loc.Start { 2875 p.addSourceMapping(e.CloseBraceLoc) 2876 } 2877 p.print("}") 2878 if wrap { 2879 p.print(")") 2880 } 2881 2882 case *js_ast.EBoolean: 2883 p.addSourceMapping(expr.Loc) 2884 if p.options.MinifySyntax { 2885 if level >= js_ast.LPrefix { 2886 if e.Value { 2887 p.print("(!0)") 2888 } else { 2889 p.print("(!1)") 2890 } 2891 } else { 2892 if e.Value { 2893 p.print("!0") 2894 } else { 2895 p.print("!1") 2896 } 2897 } 2898 } else { 2899 p.printSpaceBeforeIdentifier() 2900 if e.Value { 2901 p.print("true") 2902 } else { 2903 p.print("false") 2904 } 2905 } 2906 2907 case *js_ast.EString: 2908 var flags printQuotedFlags 2909 if e.ContainsUniqueKey { 2910 flags = printQuotedNoWrap 2911 } 2912 p.addSourceMapping(expr.Loc) 2913 2914 if !p.options.MinifyWhitespace && e.HasPropertyKeyComment { 2915 p.print("/* @__KEY__ */ ") 2916 } 2917 2918 // If this was originally a template literal, print it as one as long as we're not minifying 2919 if e.PreferTemplate && !p.options.MinifySyntax && !p.options.UnsupportedFeatures.Has(compat.TemplateLiteral) { 2920 p.print("`") 2921 p.printUnquotedUTF16(e.Value, '`', flags) 2922 p.print("`") 2923 return 2924 } 2925 2926 p.printQuotedUTF16(e.Value, flags|printQuotedAllowBacktick) 2927 2928 case *js_ast.ETemplate: 2929 if e.TagOrNil.Data == nil && (p.options.MinifySyntax || p.wasLazyExport) { 2930 // Inline enums and mangled properties when minifying 2931 var replaced []js_ast.TemplatePart 2932 for i, part := range e.Parts { 2933 var inlinedValue js_ast.E 2934 switch e2 := part.Value.Data.(type) { 2935 case *js_ast.ENameOfSymbol: 2936 inlinedValue = &js_ast.EString{Value: helpers.StringToUTF16(p.mangledPropName(e2.Ref))} 2937 case *js_ast.EDot: 2938 if value, ok := p.tryToGetImportedEnumValue(e2.Target, e2.Name); ok { 2939 if value.String != nil { 2940 inlinedValue = &js_ast.EString{Value: value.String} 2941 } else { 2942 inlinedValue = &js_ast.ENumber{Value: value.Number} 2943 } 2944 } 2945 } 2946 if inlinedValue != nil { 2947 if replaced == nil { 2948 replaced = make([]js_ast.TemplatePart, 0, len(e.Parts)) 2949 replaced = append(replaced, e.Parts[:i]...) 2950 } 2951 part.Value.Data = inlinedValue 2952 replaced = append(replaced, part) 2953 } else if replaced != nil { 2954 replaced = append(replaced, part) 2955 } 2956 } 2957 if replaced != nil { 2958 copy := *e 2959 copy.Parts = replaced 2960 switch e2 := js_ast.InlinePrimitivesIntoTemplate(logger.Loc{}, ©).Data.(type) { 2961 case *js_ast.EString: 2962 p.printQuotedUTF16(e2.Value, printQuotedAllowBacktick) 2963 return 2964 case *js_ast.ETemplate: 2965 e = e2 2966 } 2967 } 2968 2969 // Convert no-substitution template literals into strings if it's smaller 2970 if len(e.Parts) == 0 { 2971 p.addSourceMapping(expr.Loc) 2972 p.printQuotedUTF16(e.HeadCooked, printQuotedAllowBacktick) 2973 return 2974 } 2975 } 2976 2977 if e.TagOrNil.Data != nil { 2978 tagIsPropertyAccess := false 2979 switch e.TagOrNil.Data.(type) { 2980 case *js_ast.EDot, *js_ast.EIndex: 2981 tagIsPropertyAccess = true 2982 } 2983 if !e.TagWasOriginallyPropertyAccess && tagIsPropertyAccess { 2984 // Prevent "x``" from becoming "y.z``" 2985 p.print("(0,") 2986 p.printSpace() 2987 p.printExpr(e.TagOrNil, js_ast.LLowest, isCallTargetOrTemplateTag) 2988 p.print(")") 2989 } else if js_ast.IsOptionalChain(e.TagOrNil) { 2990 // Optional chains are forbidden in template tags 2991 p.print("(") 2992 p.printExpr(e.TagOrNil, js_ast.LLowest, isCallTargetOrTemplateTag) 2993 p.print(")") 2994 } else { 2995 p.printExpr(e.TagOrNil, js_ast.LPostfix, isCallTargetOrTemplateTag) 2996 } 2997 } else { 2998 p.addSourceMapping(expr.Loc) 2999 } 3000 p.print("`") 3001 if e.TagOrNil.Data != nil { 3002 p.print(e.HeadRaw) 3003 } else { 3004 p.printUnquotedUTF16(e.HeadCooked, '`', 0) 3005 } 3006 for _, part := range e.Parts { 3007 p.print("${") 3008 p.printExpr(part.Value, js_ast.LLowest, 0) 3009 p.addSourceMapping(part.TailLoc) 3010 p.print("}") 3011 if e.TagOrNil.Data != nil { 3012 p.print(part.TailRaw) 3013 } else { 3014 p.printUnquotedUTF16(part.TailCooked, '`', 0) 3015 } 3016 } 3017 p.print("`") 3018 3019 case *js_ast.ERegExp: 3020 buffer := p.js 3021 n := len(buffer) 3022 3023 // Avoid forming a single-line comment or "</script" sequence 3024 if !p.options.UnsupportedFeatures.Has(compat.InlineScript) && n > 0 { 3025 if last := buffer[n-1]; last == '/' || (last == '<' && len(e.Value) >= 7 && strings.EqualFold(e.Value[:7], "/script")) { 3026 p.print(" ") 3027 } 3028 } 3029 3030 p.addSourceMapping(expr.Loc) 3031 p.print(e.Value) 3032 3033 // Need a space before the next identifier to avoid it turning into flags 3034 p.prevRegExpEnd = len(p.js) 3035 3036 case *js_ast.EInlinedEnum: 3037 p.printExpr(e.Value, level, flags) 3038 3039 if !p.options.MinifyWhitespace && !p.options.MinifyIdentifiers { 3040 p.print(" /* ") 3041 p.print(e.Comment) 3042 p.print(" */") 3043 } 3044 3045 case *js_ast.EBigInt: 3046 p.printSpaceBeforeIdentifier() 3047 p.addSourceMapping(expr.Loc) 3048 p.print(e.Value) 3049 p.print("n") 3050 3051 case *js_ast.ENumber: 3052 p.addSourceMapping(expr.Loc) 3053 p.printNumber(e.Value, level) 3054 3055 case *js_ast.EIdentifier: 3056 name := p.renamer.NameForSymbol(e.Ref) 3057 wrap := len(p.js) == p.forOfInitStart && (name == "let" || 3058 ((flags&isFollowedByOf) != 0 && (flags&isInsideForAwait) == 0 && name == "async")) 3059 3060 if wrap { 3061 p.print("(") 3062 } 3063 3064 p.printSpaceBeforeIdentifier() 3065 p.addSourceMappingForName(expr.Loc, name, e.Ref) 3066 p.printIdentifier(name) 3067 3068 if wrap { 3069 p.print(")") 3070 } 3071 3072 case *js_ast.EImportIdentifier: 3073 // Potentially use a property access instead of an identifier 3074 ref := ast.FollowSymbols(p.symbols, e.Ref) 3075 symbol := p.symbols.Get(ref) 3076 3077 if symbol.ImportItemStatus == ast.ImportItemMissing { 3078 p.printUndefined(expr.Loc, level) 3079 } else if symbol.NamespaceAlias != nil { 3080 wrap := p.callTarget == e && e.WasOriginallyIdentifier 3081 if wrap { 3082 p.print("(0,") 3083 p.printSpace() 3084 } 3085 p.printSpaceBeforeIdentifier() 3086 p.addSourceMapping(expr.Loc) 3087 p.printIdentifier(p.renamer.NameForSymbol(symbol.NamespaceAlias.NamespaceRef)) 3088 alias := symbol.NamespaceAlias.Alias 3089 if !e.PreferQuotedKey && p.canPrintIdentifier(alias) { 3090 p.print(".") 3091 p.addSourceMappingForName(expr.Loc, alias, ref) 3092 p.printIdentifier(alias) 3093 } else { 3094 p.print("[") 3095 p.addSourceMappingForName(expr.Loc, alias, ref) 3096 p.printQuotedUTF8(alias, printQuotedAllowBacktick) 3097 p.print("]") 3098 } 3099 if wrap { 3100 p.print(")") 3101 } 3102 } else if value := p.options.ConstValues[ref]; value.Kind != js_ast.ConstValueNone { 3103 // Handle inlined constants 3104 p.printExpr(js_ast.ConstValueToExpr(expr.Loc, value), level, flags) 3105 } else { 3106 p.printSpaceBeforeIdentifier() 3107 name := p.renamer.NameForSymbol(ref) 3108 p.addSourceMappingForName(expr.Loc, name, ref) 3109 p.printIdentifier(name) 3110 } 3111 3112 case *js_ast.EAwait: 3113 wrap := level >= js_ast.LPrefix 3114 3115 if wrap { 3116 p.print("(") 3117 } 3118 3119 p.printSpaceBeforeIdentifier() 3120 p.addSourceMapping(expr.Loc) 3121 p.print("await") 3122 p.printSpace() 3123 p.printExpr(e.Value, js_ast.LPrefix-1, 0) 3124 3125 if wrap { 3126 p.print(")") 3127 } 3128 3129 case *js_ast.EYield: 3130 wrap := level >= js_ast.LAssign 3131 3132 if wrap { 3133 p.print("(") 3134 } 3135 3136 p.printSpaceBeforeIdentifier() 3137 p.addSourceMapping(expr.Loc) 3138 p.print("yield") 3139 3140 if e.ValueOrNil.Data != nil { 3141 if e.IsStar { 3142 p.print("*") 3143 } 3144 p.printSpace() 3145 p.printExprWithoutLeadingNewline(e.ValueOrNil, js_ast.LYield, 0) 3146 } 3147 3148 if wrap { 3149 p.print(")") 3150 } 3151 3152 case *js_ast.EUnary: 3153 entry := js_ast.OpTable[e.Op] 3154 wrap := level >= entry.Level 3155 3156 if wrap { 3157 p.print("(") 3158 } 3159 3160 if !e.Op.IsPrefix() { 3161 p.printExpr(e.Value, js_ast.LPostfix-1, parentWasUnaryOrBinaryOrIfTest) 3162 } 3163 3164 if entry.IsKeyword { 3165 p.printSpaceBeforeIdentifier() 3166 if e.Op.IsPrefix() { 3167 p.addSourceMapping(expr.Loc) 3168 } 3169 p.print(entry.Text) 3170 p.printSpace() 3171 } else { 3172 p.printSpaceBeforeOperator(e.Op) 3173 if e.Op.IsPrefix() { 3174 p.addSourceMapping(expr.Loc) 3175 } 3176 p.print(entry.Text) 3177 p.prevOp = e.Op 3178 p.prevOpEnd = len(p.js) 3179 } 3180 3181 if e.Op.IsPrefix() { 3182 valueFlags := parentWasUnaryOrBinaryOrIfTest 3183 if e.Op == js_ast.UnOpDelete { 3184 valueFlags |= isDeleteTarget 3185 } 3186 3187 // Never turn "typeof (0, x)" into "typeof x" or "delete (0, x)" into "delete x" 3188 if (e.Op == js_ast.UnOpTypeof && !e.WasOriginallyTypeofIdentifier && p.isUnboundIdentifier(e.Value)) || 3189 (e.Op == js_ast.UnOpDelete && !e.WasOriginallyDeleteOfIdentifierOrPropertyAccess && p.isIdentifierOrNumericConstantOrPropertyAccess(e.Value)) { 3190 p.print("(0,") 3191 p.printSpace() 3192 p.printExpr(e.Value, js_ast.LPrefix-1, valueFlags) 3193 p.print(")") 3194 } else { 3195 p.printExpr(e.Value, js_ast.LPrefix-1, valueFlags) 3196 } 3197 } 3198 3199 if wrap { 3200 p.print(")") 3201 } 3202 3203 case *js_ast.EBinary: 3204 // The handling of binary expressions is convoluted because we're using 3205 // iteration on the heap instead of recursion on the call stack to avoid 3206 // stack overflow for deeply-nested ASTs. See the comments for the similar 3207 // code in the JavaScript parser for details. 3208 v := binaryExprVisitor{ 3209 e: e, 3210 level: level, 3211 flags: flags, 3212 } 3213 3214 // Use a single stack to reduce allocation overhead 3215 stackBottom := len(p.binaryExprStack) 3216 3217 for { 3218 // Check whether this node is a special case, and stop if it is 3219 if !v.checkAndPrepare(p) { 3220 break 3221 } 3222 3223 left := v.e.Left 3224 leftBinary, ok := left.Data.(*js_ast.EBinary) 3225 3226 // Stop iterating if iteration doesn't apply to the left node 3227 if !ok { 3228 p.printExpr(left, v.leftLevel, v.leftFlags) 3229 v.visitRightAndFinish(p) 3230 break 3231 } 3232 3233 // Manually run the code at the start of "printExpr" 3234 p.printExprCommentsAtLoc(left.Loc) 3235 3236 // Only allocate heap memory on the stack for nested binary expressions 3237 p.binaryExprStack = append(p.binaryExprStack, v) 3238 v = binaryExprVisitor{ 3239 e: leftBinary, 3240 level: v.leftLevel, 3241 flags: v.leftFlags, 3242 } 3243 } 3244 3245 // Process all binary operations from the deepest-visited node back toward 3246 // our original top-level binary operation 3247 for { 3248 n := len(p.binaryExprStack) - 1 3249 if n < stackBottom { 3250 break 3251 } 3252 v := p.binaryExprStack[n] 3253 p.binaryExprStack = p.binaryExprStack[:n] 3254 v.visitRightAndFinish(p) 3255 } 3256 3257 default: 3258 panic(fmt.Sprintf("Unexpected expression of type %T", expr.Data)) 3259 } 3260 } 3261 3262 // The handling of binary expressions is convoluted because we're using 3263 // iteration on the heap instead of recursion on the call stack to avoid 3264 // stack overflow for deeply-nested ASTs. See the comments for the similar 3265 // code in the JavaScript parser for details. 3266 type binaryExprVisitor struct { 3267 // Inputs 3268 e *js_ast.EBinary 3269 level js_ast.L 3270 flags printExprFlags 3271 3272 // Input for visiting the left child 3273 leftLevel js_ast.L 3274 leftFlags printExprFlags 3275 3276 // "Local variables" passed from "checkAndPrepare" to "visitRightAndFinish" 3277 entry js_ast.OpTableEntry 3278 wrap bool 3279 rightLevel js_ast.L 3280 } 3281 3282 func (v *binaryExprVisitor) checkAndPrepare(p *printer) bool { 3283 e := v.e 3284 3285 // If this is a comma operator then either the result is unused (and we 3286 // should have already simplified unused expressions), or the result is used 3287 // (and we can still simplify unused expressions inside the left operand) 3288 if e.Op == js_ast.BinOpComma { 3289 if (v.flags & didAlreadySimplifyUnusedExprs) == 0 { 3290 left := p.simplifyUnusedExpr(e.Left) 3291 right := e.Right 3292 if (v.flags & exprResultIsUnused) != 0 { 3293 right = p.simplifyUnusedExpr(right) 3294 } 3295 if left.Data != e.Left.Data || right.Data != e.Right.Data { 3296 // Pass a flag so we don't needlessly re-simplify the same expression 3297 p.printExpr(p.guardAgainstBehaviorChangeDueToSubstitution(js_ast.JoinWithComma(left, right), v.flags), v.level, v.flags|didAlreadySimplifyUnusedExprs) 3298 return false 3299 } 3300 } else { 3301 // Pass a flag so we don't needlessly re-simplify the same expression 3302 v.flags |= didAlreadySimplifyUnusedExprs 3303 } 3304 } 3305 3306 v.entry = js_ast.OpTable[e.Op] 3307 v.wrap = v.level >= v.entry.Level || (e.Op == js_ast.BinOpIn && (v.flags&forbidIn) != 0) 3308 3309 // Destructuring assignments must be parenthesized 3310 if n := len(p.js); p.stmtStart == n || p.arrowExprStart == n { 3311 if _, ok := e.Left.Data.(*js_ast.EObject); ok { 3312 v.wrap = true 3313 } 3314 } 3315 3316 if v.wrap { 3317 p.print("(") 3318 v.flags &= ^forbidIn 3319 } 3320 3321 v.leftLevel = v.entry.Level - 1 3322 v.rightLevel = v.entry.Level - 1 3323 3324 if e.Op.IsRightAssociative() { 3325 v.leftLevel = v.entry.Level 3326 } 3327 if e.Op.IsLeftAssociative() { 3328 v.rightLevel = v.entry.Level 3329 } 3330 3331 switch e.Op { 3332 case js_ast.BinOpNullishCoalescing: 3333 // "??" can't directly contain "||" or "&&" without being wrapped in parentheses 3334 if left, ok := e.Left.Data.(*js_ast.EBinary); ok && (left.Op == js_ast.BinOpLogicalOr || left.Op == js_ast.BinOpLogicalAnd) { 3335 v.leftLevel = js_ast.LPrefix 3336 } 3337 if right, ok := e.Right.Data.(*js_ast.EBinary); ok && (right.Op == js_ast.BinOpLogicalOr || right.Op == js_ast.BinOpLogicalAnd) { 3338 v.rightLevel = js_ast.LPrefix 3339 } 3340 3341 case js_ast.BinOpPow: 3342 // "**" can't contain certain unary expressions 3343 if left, ok := e.Left.Data.(*js_ast.EUnary); ok && left.Op.UnaryAssignTarget() == js_ast.AssignTargetNone { 3344 v.leftLevel = js_ast.LCall 3345 } else if _, ok := e.Left.Data.(*js_ast.EAwait); ok { 3346 v.leftLevel = js_ast.LCall 3347 } else if _, ok := e.Left.Data.(*js_ast.EUndefined); ok { 3348 // Undefined is printed as "void 0" 3349 v.leftLevel = js_ast.LCall 3350 } else if _, ok := e.Left.Data.(*js_ast.ENumber); ok { 3351 // Negative numbers are printed using a unary operator 3352 v.leftLevel = js_ast.LCall 3353 } else if p.options.MinifySyntax { 3354 // When minifying, booleans are printed as "!0 and "!1" 3355 if _, ok := e.Left.Data.(*js_ast.EBoolean); ok { 3356 v.leftLevel = js_ast.LCall 3357 } 3358 } 3359 } 3360 3361 // Special-case "#foo in bar" 3362 if private, ok := e.Left.Data.(*js_ast.EPrivateIdentifier); ok && e.Op == js_ast.BinOpIn { 3363 name := p.renamer.NameForSymbol(private.Ref) 3364 p.addSourceMappingForName(e.Left.Loc, name, private.Ref) 3365 p.printIdentifier(name) 3366 v.visitRightAndFinish(p) 3367 return false 3368 } 3369 3370 if e.Op == js_ast.BinOpComma { 3371 // The result of the left operand of the comma operator is unused 3372 v.leftFlags = (v.flags & forbidIn) | exprResultIsUnused | parentWasUnaryOrBinaryOrIfTest 3373 } else { 3374 v.leftFlags = (v.flags & forbidIn) | parentWasUnaryOrBinaryOrIfTest 3375 } 3376 return true 3377 } 3378 3379 func (v *binaryExprVisitor) visitRightAndFinish(p *printer) { 3380 e := v.e 3381 3382 if e.Op != js_ast.BinOpComma { 3383 p.printSpace() 3384 } 3385 3386 if v.entry.IsKeyword { 3387 p.printSpaceBeforeIdentifier() 3388 p.print(v.entry.Text) 3389 } else { 3390 p.printSpaceBeforeOperator(e.Op) 3391 p.print(v.entry.Text) 3392 p.prevOp = e.Op 3393 p.prevOpEnd = len(p.js) 3394 } 3395 3396 if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() { 3397 p.printSpace() 3398 } 3399 3400 if e.Op == js_ast.BinOpComma { 3401 // The result of the right operand of the comma operator is unused if the caller doesn't use it 3402 p.printExpr(e.Right, v.rightLevel, (v.flags&(forbidIn|exprResultIsUnused))|parentWasUnaryOrBinaryOrIfTest) 3403 } else { 3404 p.printExpr(e.Right, v.rightLevel, (v.flags&forbidIn)|parentWasUnaryOrBinaryOrIfTest) 3405 } 3406 3407 if v.wrap { 3408 p.print(")") 3409 } 3410 } 3411 3412 func (p *printer) isUnboundEvalIdentifier(value js_ast.Expr) bool { 3413 if id, ok := value.Data.(*js_ast.EIdentifier); ok { 3414 // Using the original name here is ok since unbound symbols are not renamed 3415 symbol := p.symbols.Get(ast.FollowSymbols(p.symbols, id.Ref)) 3416 return symbol.Kind == ast.SymbolUnbound && symbol.OriginalName == "eval" 3417 } 3418 return false 3419 } 3420 3421 // Convert an integer to a byte slice without any allocations 3422 func (p *printer) smallIntToBytes(n int) []byte { 3423 wasNegative := n < 0 3424 if wasNegative { 3425 // This assumes that -math.MinInt isn't a problem. This is fine because 3426 // these integers are floating-point exponents which never go up that high. 3427 n = -n 3428 } 3429 3430 bytes := p.intToBytesBuffer[:] 3431 start := len(bytes) 3432 3433 // Write out the number from the end to the front 3434 for { 3435 start-- 3436 bytes[start] = '0' + byte(n%10) 3437 n /= 10 3438 if n == 0 { 3439 break 3440 } 3441 } 3442 3443 // Stick a negative sign on the front if needed 3444 if wasNegative { 3445 start-- 3446 bytes[start] = '-' 3447 } 3448 3449 return bytes[start:] 3450 } 3451 3452 func parseSmallInt(bytes []byte) int { 3453 wasNegative := bytes[0] == '-' 3454 if wasNegative { 3455 bytes = bytes[1:] 3456 } 3457 3458 // Parse the integer without any error checking. This doesn't need to handle 3459 // integer overflow because these integers are floating-point exponents which 3460 // never go up that high. 3461 n := 0 3462 for _, c := range bytes { 3463 n = n*10 + int(c-'0') 3464 } 3465 3466 if wasNegative { 3467 return -n 3468 } 3469 return n 3470 } 3471 3472 func (p *printer) printNonNegativeFloat(absValue float64) { 3473 // We can avoid the slow call to strconv.FormatFloat() for integers less than 3474 // 1000 because we know that exponential notation will always be longer than 3475 // the integer representation. This is not the case for 1000 which is "1e3". 3476 if absValue < 1000 { 3477 if asInt := int64(absValue); absValue == float64(asInt) { 3478 p.printBytes(p.smallIntToBytes(int(asInt))) 3479 3480 // Integers always need a space before "." to avoid making a decimal point 3481 p.needSpaceBeforeDot = len(p.js) 3482 return 3483 } 3484 } 3485 3486 // Format this number into a byte slice so we can mutate it in place without 3487 // further reallocation 3488 result := []byte(strconv.FormatFloat(absValue, 'g', -1, 64)) 3489 3490 // Simplify the exponent 3491 // "e+05" => "e5" 3492 // "e-05" => "e-5" 3493 if e := bytes.LastIndexByte(result, 'e'); e != -1 { 3494 from := e + 1 3495 to := from 3496 3497 switch result[from] { 3498 case '+': 3499 // Strip off the leading "+" 3500 from++ 3501 3502 case '-': 3503 // Skip past the leading "-" 3504 to++ 3505 from++ 3506 } 3507 3508 // Strip off leading zeros 3509 for from < len(result) && result[from] == '0' { 3510 from++ 3511 } 3512 3513 result = append(result[:to], result[from:]...) 3514 } 3515 3516 dot := bytes.IndexByte(result, '.') 3517 3518 if dot == 1 && result[0] == '0' { 3519 // Simplify numbers starting with "0." 3520 afterDot := 2 3521 3522 // Strip off the leading zero when minifying 3523 // "0.5" => ".5" 3524 if p.options.MinifyWhitespace { 3525 result = result[1:] 3526 afterDot-- 3527 } 3528 3529 // Try using an exponent 3530 // "0.001" => "1e-3" 3531 if result[afterDot] == '0' { 3532 i := afterDot + 1 3533 for result[i] == '0' { 3534 i++ 3535 } 3536 remaining := result[i:] 3537 exponent := p.smallIntToBytes(afterDot - i - len(remaining)) 3538 3539 // Only switch if it's actually shorter 3540 if len(result) > len(remaining)+1+len(exponent) { 3541 result = append(append(remaining, 'e'), exponent...) 3542 } 3543 } 3544 } else if dot != -1 { 3545 // Try to get rid of a "." and maybe also an "e" 3546 if e := bytes.LastIndexByte(result, 'e'); e != -1 { 3547 integer := result[:dot] 3548 fraction := result[dot+1 : e] 3549 exponent := parseSmallInt(result[e+1:]) - len(fraction) 3550 3551 // Handle small exponents by appending zeros instead 3552 if exponent >= 0 && exponent <= 2 { 3553 // "1.2e1" => "12" 3554 // "1.2e2" => "120" 3555 // "1.2e3" => "1200" 3556 if len(result) >= len(integer)+len(fraction)+exponent { 3557 result = append(integer, fraction...) 3558 for i := 0; i < exponent; i++ { 3559 result = append(result, '0') 3560 } 3561 } 3562 } else { 3563 // "1.2e4" => "12e3" 3564 exponent := p.smallIntToBytes(exponent) 3565 if len(result) >= len(integer)+len(fraction)+1+len(exponent) { 3566 result = append(append(append(integer, fraction...), 'e'), exponent...) 3567 } 3568 } 3569 } 3570 } else if result[len(result)-1] == '0' { 3571 // Simplify numbers ending with "0" by trying to use an exponent 3572 // "1000" => "1e3" 3573 i := len(result) - 1 3574 for i > 0 && result[i-1] == '0' { 3575 i-- 3576 } 3577 remaining := result[:i] 3578 exponent := p.smallIntToBytes(len(result) - i) 3579 3580 // Only switch if it's actually shorter 3581 if len(result) > len(remaining)+1+len(exponent) { 3582 result = append(append(remaining, 'e'), exponent...) 3583 } 3584 } 3585 3586 // Numbers in this range can potentially be printed with one fewer byte as 3587 // hex. This compares against 0xFFFF_FFFF_FFFF_F800 instead of comparing 3588 // against 0xFFFF_FFFF_FFFF_FFFF because 0xFFFF_FFFF_FFFF_FFFF when converted 3589 // to float64 rounds up to 0x1_0000_0000_0000_0180, which can no longer fit 3590 // into uint64. In Go, the result of converting float64 to uint64 outside of 3591 // the uint64 range is implementation-dependent and is different on amd64 vs. 3592 // arm64. The float64 value 0xFFFF_FFFF_FFFF_F800 is the biggest value that 3593 // is below the float64 value 0x1_0000_0000_0000_0180, so we use that instead. 3594 if p.options.MinifyWhitespace && absValue >= 1_000_000_000_000 && absValue <= 0xFFFF_FFFF_FFFF_F800 { 3595 if asInt := uint64(absValue); absValue == float64(asInt) { 3596 if hex := strconv.FormatUint(asInt, 16); 2+len(hex) < len(result) { 3597 result = append(append(result[:0], '0', 'x'), hex...) 3598 } 3599 } 3600 } 3601 3602 p.printBytes(result) 3603 3604 // We'll need a space before "." if it could be parsed as a decimal point 3605 if !bytes.ContainsAny(result, ".ex") { 3606 p.needSpaceBeforeDot = len(p.js) 3607 } 3608 } 3609 3610 func (p *printer) printDeclStmt(isExport bool, keyword string, decls []js_ast.Decl) { 3611 p.printIndent() 3612 p.printSpaceBeforeIdentifier() 3613 if isExport { 3614 p.print("export ") 3615 } 3616 p.printDecls(keyword, decls, 0) 3617 p.printSemicolonAfterStatement() 3618 } 3619 3620 func (p *printer) printForLoopInit(init js_ast.Stmt, flags printExprFlags) { 3621 switch s := init.Data.(type) { 3622 case *js_ast.SExpr: 3623 p.printExpr(s.Value, js_ast.LLowest, flags|exprResultIsUnused) 3624 case *js_ast.SLocal: 3625 switch s.Kind { 3626 case js_ast.LocalAwaitUsing: 3627 p.printDecls("await using", s.Decls, flags) 3628 case js_ast.LocalConst: 3629 p.printDecls("const", s.Decls, flags) 3630 case js_ast.LocalLet: 3631 p.printDecls("let", s.Decls, flags) 3632 case js_ast.LocalUsing: 3633 p.printDecls("using", s.Decls, flags) 3634 case js_ast.LocalVar: 3635 p.printDecls("var", s.Decls, flags) 3636 } 3637 default: 3638 panic("Internal error") 3639 } 3640 } 3641 3642 func (p *printer) printDecls(keyword string, decls []js_ast.Decl, flags printExprFlags) { 3643 p.print(keyword) 3644 p.printSpace() 3645 3646 for i, decl := range decls { 3647 if i != 0 { 3648 p.print(",") 3649 if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() { 3650 p.printSpace() 3651 } 3652 } 3653 p.printBinding(decl.Binding) 3654 3655 if decl.ValueOrNil.Data != nil { 3656 p.printSpace() 3657 p.print("=") 3658 p.printSpace() 3659 p.printExprWithoutLeadingNewline(decl.ValueOrNil, js_ast.LComma, flags) 3660 } 3661 } 3662 } 3663 3664 func (p *printer) printBody(body js_ast.Stmt, isSingleLine bool) { 3665 if block, ok := body.Data.(*js_ast.SBlock); ok { 3666 p.printSpace() 3667 p.printBlock(body.Loc, *block) 3668 p.printNewline() 3669 } else if isSingleLine { 3670 p.printNextIndentAsSpace = true 3671 p.printStmt(body, 0) 3672 } else { 3673 p.printNewline() 3674 p.options.Indent++ 3675 p.printStmt(body, 0) 3676 p.options.Indent-- 3677 } 3678 } 3679 3680 func (p *printer) printBlock(loc logger.Loc, block js_ast.SBlock) { 3681 p.addSourceMapping(loc) 3682 p.print("{") 3683 p.printNewline() 3684 3685 p.options.Indent++ 3686 for _, stmt := range block.Stmts { 3687 p.printSemicolonIfNeeded() 3688 p.printStmt(stmt, canOmitStatement) 3689 } 3690 p.options.Indent-- 3691 p.needsSemicolon = false 3692 3693 p.printIndent() 3694 if block.CloseBraceLoc.Start > loc.Start { 3695 p.addSourceMapping(block.CloseBraceLoc) 3696 } 3697 p.print("}") 3698 } 3699 3700 func wrapToAvoidAmbiguousElse(s js_ast.S) bool { 3701 for { 3702 switch current := s.(type) { 3703 case *js_ast.SIf: 3704 if current.NoOrNil.Data == nil { 3705 return true 3706 } 3707 s = current.NoOrNil.Data 3708 3709 case *js_ast.SFor: 3710 s = current.Body.Data 3711 3712 case *js_ast.SForIn: 3713 s = current.Body.Data 3714 3715 case *js_ast.SForOf: 3716 s = current.Body.Data 3717 3718 case *js_ast.SWhile: 3719 s = current.Body.Data 3720 3721 case *js_ast.SWith: 3722 s = current.Body.Data 3723 3724 case *js_ast.SLabel: 3725 s = current.Stmt.Data 3726 3727 default: 3728 return false 3729 } 3730 } 3731 } 3732 3733 func (p *printer) printIf(s *js_ast.SIf) { 3734 p.printSpaceBeforeIdentifier() 3735 p.print("if") 3736 p.printSpace() 3737 p.print("(") 3738 if p.willPrintExprCommentsAtLoc(s.Test.Loc) { 3739 p.printNewline() 3740 p.options.Indent++ 3741 p.printIndent() 3742 p.printExpr(s.Test, js_ast.LLowest, 0) 3743 p.printNewline() 3744 p.options.Indent-- 3745 p.printIndent() 3746 } else { 3747 p.printExpr(s.Test, js_ast.LLowest, 0) 3748 } 3749 p.print(")") 3750 3751 // Simplify the else branch, which may disappear entirely 3752 no := s.NoOrNil 3753 if expr, ok := no.Data.(*js_ast.SExpr); ok { 3754 if value := p.simplifyUnusedExpr(expr.Value); value.Data == nil { 3755 no.Data = nil 3756 } else if value.Data != expr.Value.Data { 3757 no.Data = &js_ast.SExpr{Value: value} 3758 } 3759 } 3760 3761 if yes, ok := s.Yes.Data.(*js_ast.SBlock); ok { 3762 p.printSpace() 3763 p.printBlock(s.Yes.Loc, *yes) 3764 3765 if no.Data != nil { 3766 p.printSpace() 3767 } else { 3768 p.printNewline() 3769 } 3770 } else if wrapToAvoidAmbiguousElse(s.Yes.Data) { 3771 p.printSpace() 3772 p.print("{") 3773 p.printNewline() 3774 3775 p.options.Indent++ 3776 p.printStmt(s.Yes, canOmitStatement) 3777 p.options.Indent-- 3778 p.needsSemicolon = false 3779 3780 p.printIndent() 3781 p.print("}") 3782 3783 if no.Data != nil { 3784 p.printSpace() 3785 } else { 3786 p.printNewline() 3787 } 3788 } else { 3789 p.printBody(s.Yes, s.IsSingleLineYes) 3790 3791 if no.Data != nil { 3792 p.printIndent() 3793 } 3794 } 3795 3796 if no.Data != nil { 3797 p.printSemicolonIfNeeded() 3798 p.printSpaceBeforeIdentifier() 3799 p.print("else") 3800 3801 if block, ok := no.Data.(*js_ast.SBlock); ok { 3802 p.printSpace() 3803 p.printBlock(no.Loc, *block) 3804 p.printNewline() 3805 } else if ifStmt, ok := no.Data.(*js_ast.SIf); ok { 3806 p.printIf(ifStmt) 3807 } else { 3808 p.printBody(no, s.IsSingleLineNo) 3809 } 3810 } 3811 } 3812 3813 func (p *printer) printIndentedComment(text string) { 3814 // Avoid generating a comment containing the character sequence "</script" 3815 if !p.options.UnsupportedFeatures.Has(compat.InlineScript) { 3816 text = helpers.EscapeClosingTag(text, "/script") 3817 } 3818 3819 if strings.HasPrefix(text, "/*") { 3820 // Re-indent multi-line comments 3821 for { 3822 newline := strings.IndexByte(text, '\n') 3823 if newline == -1 { 3824 break 3825 } 3826 p.print(text[:newline+1]) 3827 p.printIndent() 3828 text = text[newline+1:] 3829 } 3830 p.print(text) 3831 p.printNewline() 3832 } else { 3833 // Print a mandatory newline after single-line comments 3834 p.print(text) 3835 p.print("\n") 3836 } 3837 } 3838 3839 func (p *printer) printPath(importRecordIndex uint32, importKind ast.ImportKind) { 3840 record := p.importRecords[importRecordIndex] 3841 p.addSourceMapping(record.Range.Loc) 3842 p.printQuotedUTF8(record.Path.Text, printQuotedNoWrap) 3843 3844 if p.options.NeedsMetafile { 3845 external := "" 3846 if (record.Flags & ast.ShouldNotBeExternalInMetafile) == 0 { 3847 external = ",\n \"external\": true" 3848 } 3849 p.jsonMetadataImports = append(p.jsonMetadataImports, fmt.Sprintf("\n {\n \"path\": %s,\n \"kind\": %s%s\n }", 3850 helpers.QuoteForJSON(record.Path.Text, p.options.ASCIIOnly), 3851 helpers.QuoteForJSON(importKind.StringForMetafile(), p.options.ASCIIOnly), 3852 external)) 3853 } 3854 3855 if record.AssertOrWith != nil && importKind == ast.ImportStmt { 3856 feature := compat.ImportAttributes 3857 if record.AssertOrWith.Keyword == ast.AssertKeyword { 3858 feature = compat.ImportAssertions 3859 } 3860 3861 // Omit import assertions/attributes on this import statement if they would cause a syntax error 3862 if p.options.UnsupportedFeatures.Has(feature) { 3863 return 3864 } 3865 3866 p.printSpace() 3867 p.addSourceMapping(record.AssertOrWith.KeywordLoc) 3868 p.print(record.AssertOrWith.Keyword.String()) 3869 p.printSpace() 3870 p.printImportAssertOrWithClause(*record.AssertOrWith) 3871 } 3872 } 3873 3874 func (p *printer) printImportCallAssertOrWith(assertOrWith *ast.ImportAssertOrWith, outerIsMultiLine bool) { 3875 // Omit import assertions/attributes if we know the "import()" syntax doesn't 3876 // support a second argument (i.e. both import assertions and import 3877 // attributes aren't supported) and doing so would cause a syntax error 3878 if assertOrWith == nil || (p.options.UnsupportedFeatures.Has(compat.ImportAssertions) && p.options.UnsupportedFeatures.Has(compat.ImportAttributes)) { 3879 return 3880 } 3881 3882 isMultiLine := p.willPrintExprCommentsAtLoc(assertOrWith.KeywordLoc) || 3883 p.willPrintExprCommentsAtLoc(assertOrWith.InnerOpenBraceLoc) || 3884 p.willPrintExprCommentsAtLoc(assertOrWith.OuterCloseBraceLoc) 3885 3886 p.print(",") 3887 if outerIsMultiLine { 3888 p.printNewline() 3889 p.printIndent() 3890 } else { 3891 p.printSpace() 3892 } 3893 p.printExprCommentsAtLoc(assertOrWith.OuterOpenBraceLoc) 3894 p.addSourceMapping(assertOrWith.OuterOpenBraceLoc) 3895 p.print("{") 3896 3897 if isMultiLine { 3898 p.printNewline() 3899 p.options.Indent++ 3900 p.printIndent() 3901 } else { 3902 p.printSpace() 3903 } 3904 3905 p.printExprCommentsAtLoc(assertOrWith.KeywordLoc) 3906 p.addSourceMapping(assertOrWith.KeywordLoc) 3907 p.print(assertOrWith.Keyword.String()) 3908 p.print(":") 3909 3910 if p.willPrintExprCommentsAtLoc(assertOrWith.InnerOpenBraceLoc) { 3911 p.printNewline() 3912 p.options.Indent++ 3913 p.printIndent() 3914 p.printExprCommentsAtLoc(assertOrWith.InnerOpenBraceLoc) 3915 p.printImportAssertOrWithClause(*assertOrWith) 3916 p.options.Indent-- 3917 } else { 3918 p.printSpace() 3919 p.printImportAssertOrWithClause(*assertOrWith) 3920 } 3921 3922 if isMultiLine { 3923 p.printNewline() 3924 p.printExprCommentsAfterCloseTokenAtLoc(assertOrWith.OuterCloseBraceLoc) 3925 p.options.Indent-- 3926 p.printIndent() 3927 } else { 3928 p.printSpace() 3929 } 3930 3931 p.addSourceMapping(assertOrWith.OuterCloseBraceLoc) 3932 p.print("}") 3933 } 3934 3935 func (p *printer) printImportAssertOrWithClause(assertOrWith ast.ImportAssertOrWith) { 3936 isMultiLine := p.willPrintExprCommentsAtLoc(assertOrWith.InnerCloseBraceLoc) 3937 if !isMultiLine { 3938 for _, entry := range assertOrWith.Entries { 3939 if p.willPrintExprCommentsAtLoc(entry.KeyLoc) || p.willPrintExprCommentsAtLoc(entry.ValueLoc) { 3940 isMultiLine = true 3941 break 3942 } 3943 } 3944 } 3945 3946 p.addSourceMapping(assertOrWith.InnerOpenBraceLoc) 3947 p.print("{") 3948 if isMultiLine { 3949 p.options.Indent++ 3950 } 3951 3952 for i, entry := range assertOrWith.Entries { 3953 if i > 0 { 3954 p.print(",") 3955 } 3956 if isMultiLine { 3957 p.printNewline() 3958 p.printIndent() 3959 } else { 3960 p.printSpace() 3961 } 3962 3963 p.printExprCommentsAtLoc(entry.KeyLoc) 3964 p.addSourceMapping(entry.KeyLoc) 3965 if !entry.PreferQuotedKey && p.canPrintIdentifierUTF16(entry.Key) { 3966 p.printSpaceBeforeIdentifier() 3967 p.printIdentifierUTF16(entry.Key) 3968 } else { 3969 p.printQuotedUTF16(entry.Key, 0) 3970 } 3971 3972 p.print(":") 3973 3974 if p.willPrintExprCommentsAtLoc(entry.ValueLoc) { 3975 p.printNewline() 3976 p.options.Indent++ 3977 p.printIndent() 3978 p.printExprCommentsAtLoc(entry.ValueLoc) 3979 p.addSourceMapping(entry.ValueLoc) 3980 p.printQuotedUTF16(entry.Value, 0) 3981 p.options.Indent-- 3982 } else { 3983 p.printSpace() 3984 p.addSourceMapping(entry.ValueLoc) 3985 p.printQuotedUTF16(entry.Value, 0) 3986 } 3987 } 3988 3989 if isMultiLine { 3990 p.printNewline() 3991 p.printExprCommentsAfterCloseTokenAtLoc(assertOrWith.InnerCloseBraceLoc) 3992 p.options.Indent-- 3993 p.printIndent() 3994 } else if len(assertOrWith.Entries) > 0 { 3995 p.printSpace() 3996 } 3997 3998 p.addSourceMapping(assertOrWith.InnerCloseBraceLoc) 3999 p.print("}") 4000 } 4001 4002 type printStmtFlags uint8 4003 4004 const ( 4005 canOmitStatement printStmtFlags = 1 << iota 4006 ) 4007 4008 func (p *printer) printStmt(stmt js_ast.Stmt, flags printStmtFlags) { 4009 if p.options.LineLimit > 0 { 4010 p.printNewlinePastLineLimit() 4011 } 4012 4013 switch s := stmt.Data.(type) { 4014 case *js_ast.SComment: 4015 text := s.Text 4016 4017 if s.IsLegalComment { 4018 switch p.options.LegalComments { 4019 case config.LegalCommentsNone: 4020 return 4021 4022 case config.LegalCommentsEndOfFile, 4023 config.LegalCommentsLinkedWithComment, 4024 config.LegalCommentsExternalWithoutComment: 4025 4026 // Don't record the same legal comment more than once per file 4027 if p.hasLegalComment == nil { 4028 p.hasLegalComment = make(map[string]struct{}) 4029 } else if _, ok := p.hasLegalComment[text]; ok { 4030 return 4031 } 4032 p.hasLegalComment[text] = struct{}{} 4033 p.extractedLegalComments = append(p.extractedLegalComments, text) 4034 return 4035 } 4036 } 4037 4038 p.printIndent() 4039 p.addSourceMapping(stmt.Loc) 4040 p.printIndentedComment(text) 4041 4042 case *js_ast.SFunction: 4043 if !p.options.MinifyWhitespace && s.Fn.HasNoSideEffectsComment { 4044 p.printIndent() 4045 p.print("// @__NO_SIDE_EFFECTS__\n") 4046 } 4047 p.addSourceMapping(stmt.Loc) 4048 p.printIndent() 4049 p.printSpaceBeforeIdentifier() 4050 if s.IsExport { 4051 p.print("export ") 4052 } 4053 if s.Fn.IsAsync { 4054 p.print("async ") 4055 } 4056 p.print("function") 4057 if s.Fn.IsGenerator { 4058 p.print("*") 4059 p.printSpace() 4060 } 4061 p.printSpaceBeforeIdentifier() 4062 name := p.renamer.NameForSymbol(s.Fn.Name.Ref) 4063 p.addSourceMappingForName(s.Fn.Name.Loc, name, s.Fn.Name.Ref) 4064 p.printIdentifier(name) 4065 p.printFn(s.Fn) 4066 p.printNewline() 4067 4068 case *js_ast.SClass: 4069 omitIndent := p.printDecorators(s.Class.Decorators, printNewlineAfterDecorator) 4070 if !omitIndent { 4071 p.printIndent() 4072 } 4073 p.printSpaceBeforeIdentifier() 4074 p.addSourceMapping(stmt.Loc) 4075 if s.IsExport { 4076 p.print("export ") 4077 } 4078 p.print("class ") 4079 name := p.renamer.NameForSymbol(s.Class.Name.Ref) 4080 p.addSourceMappingForName(s.Class.Name.Loc, name, s.Class.Name.Ref) 4081 p.printIdentifier(name) 4082 p.printClass(s.Class) 4083 p.printNewline() 4084 4085 case *js_ast.SEmpty: 4086 p.addSourceMapping(stmt.Loc) 4087 p.printIndent() 4088 p.print(";") 4089 p.printNewline() 4090 4091 case *js_ast.SExportDefault: 4092 if !p.options.MinifyWhitespace { 4093 if s2, ok := s.Value.Data.(*js_ast.SFunction); ok && s2.Fn.HasNoSideEffectsComment { 4094 p.printIndent() 4095 p.print("// @__NO_SIDE_EFFECTS__\n") 4096 } 4097 } 4098 omitIndent := false 4099 if s2, ok := s.Value.Data.(*js_ast.SClass); ok { 4100 omitIndent = p.printDecorators(s2.Class.Decorators, printNewlineAfterDecorator) 4101 } 4102 p.addSourceMapping(stmt.Loc) 4103 if !omitIndent { 4104 p.printIndent() 4105 } 4106 p.printSpaceBeforeIdentifier() 4107 p.print("export default") 4108 p.printSpace() 4109 4110 switch s2 := s.Value.Data.(type) { 4111 case *js_ast.SExpr: 4112 // Functions and classes must be wrapped to avoid confusion with their statement forms 4113 p.exportDefaultStart = len(p.js) 4114 4115 p.printExprWithoutLeadingNewline(s2.Value, js_ast.LComma, 0) 4116 p.printSemicolonAfterStatement() 4117 return 4118 4119 case *js_ast.SFunction: 4120 p.printSpaceBeforeIdentifier() 4121 if s2.Fn.IsAsync { 4122 p.print("async ") 4123 } 4124 p.print("function") 4125 if s2.Fn.IsGenerator { 4126 p.print("*") 4127 p.printSpace() 4128 } 4129 if s2.Fn.Name != nil { 4130 p.printSpaceBeforeIdentifier() 4131 name := p.renamer.NameForSymbol(s2.Fn.Name.Ref) 4132 p.addSourceMappingForName(s2.Fn.Name.Loc, name, s2.Fn.Name.Ref) 4133 p.printIdentifier(name) 4134 } 4135 p.printFn(s2.Fn) 4136 p.printNewline() 4137 4138 case *js_ast.SClass: 4139 p.printSpaceBeforeIdentifier() 4140 p.print("class") 4141 if s2.Class.Name != nil { 4142 p.print(" ") 4143 name := p.renamer.NameForSymbol(s2.Class.Name.Ref) 4144 p.addSourceMappingForName(s2.Class.Name.Loc, name, s2.Class.Name.Ref) 4145 p.printIdentifier(name) 4146 } 4147 p.printClass(s2.Class) 4148 p.printNewline() 4149 4150 default: 4151 panic("Internal error") 4152 } 4153 4154 case *js_ast.SExportStar: 4155 p.addSourceMapping(stmt.Loc) 4156 p.printIndent() 4157 p.printSpaceBeforeIdentifier() 4158 p.print("export") 4159 p.printSpace() 4160 p.print("*") 4161 p.printSpace() 4162 if s.Alias != nil { 4163 p.print("as") 4164 p.printSpace() 4165 p.printClauseAlias(s.Alias.Loc, s.Alias.OriginalName) 4166 p.printSpace() 4167 p.printSpaceBeforeIdentifier() 4168 } 4169 p.print("from") 4170 p.printSpace() 4171 p.printPath(s.ImportRecordIndex, ast.ImportStmt) 4172 p.printSemicolonAfterStatement() 4173 4174 case *js_ast.SExportClause: 4175 p.addSourceMapping(stmt.Loc) 4176 p.printIndent() 4177 p.printSpaceBeforeIdentifier() 4178 p.print("export") 4179 p.printSpace() 4180 p.print("{") 4181 4182 if !s.IsSingleLine { 4183 p.options.Indent++ 4184 } 4185 4186 for i, item := range s.Items { 4187 if i != 0 { 4188 p.print(",") 4189 } 4190 4191 if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() { 4192 if s.IsSingleLine { 4193 p.printSpace() 4194 } else { 4195 p.printNewline() 4196 p.printIndent() 4197 } 4198 } 4199 4200 name := p.renamer.NameForSymbol(item.Name.Ref) 4201 p.addSourceMappingForName(item.Name.Loc, name, item.Name.Ref) 4202 p.printIdentifier(name) 4203 if name != item.Alias { 4204 p.print(" as") 4205 p.printSpace() 4206 p.printClauseAlias(item.AliasLoc, item.Alias) 4207 } 4208 } 4209 4210 if !s.IsSingleLine { 4211 p.options.Indent-- 4212 p.printNewline() 4213 p.printIndent() 4214 } else if len(s.Items) > 0 { 4215 p.printSpace() 4216 } 4217 4218 p.print("}") 4219 p.printSemicolonAfterStatement() 4220 4221 case *js_ast.SExportFrom: 4222 p.addSourceMapping(stmt.Loc) 4223 p.printIndent() 4224 p.printSpaceBeforeIdentifier() 4225 p.print("export") 4226 p.printSpace() 4227 p.print("{") 4228 4229 if !s.IsSingleLine { 4230 p.options.Indent++ 4231 } 4232 4233 for i, item := range s.Items { 4234 if i != 0 { 4235 p.print(",") 4236 } 4237 4238 if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() { 4239 if s.IsSingleLine { 4240 p.printSpace() 4241 } else { 4242 p.printNewline() 4243 p.printIndent() 4244 } 4245 } 4246 4247 p.printClauseAlias(item.Name.Loc, item.OriginalName) 4248 if item.OriginalName != item.Alias { 4249 p.printSpace() 4250 p.printSpaceBeforeIdentifier() 4251 p.print("as") 4252 p.printSpace() 4253 p.printClauseAlias(item.AliasLoc, item.Alias) 4254 } 4255 } 4256 4257 if !s.IsSingleLine { 4258 p.options.Indent-- 4259 p.printNewline() 4260 p.printIndent() 4261 } else if len(s.Items) > 0 { 4262 p.printSpace() 4263 } 4264 4265 p.print("}") 4266 p.printSpace() 4267 p.print("from") 4268 p.printSpace() 4269 p.printPath(s.ImportRecordIndex, ast.ImportStmt) 4270 p.printSemicolonAfterStatement() 4271 4272 case *js_ast.SLocal: 4273 p.addSourceMapping(stmt.Loc) 4274 switch s.Kind { 4275 case js_ast.LocalAwaitUsing: 4276 p.printDeclStmt(s.IsExport, "await using", s.Decls) 4277 case js_ast.LocalConst: 4278 p.printDeclStmt(s.IsExport, "const", s.Decls) 4279 case js_ast.LocalLet: 4280 p.printDeclStmt(s.IsExport, "let", s.Decls) 4281 case js_ast.LocalUsing: 4282 p.printDeclStmt(s.IsExport, "using", s.Decls) 4283 case js_ast.LocalVar: 4284 p.printDeclStmt(s.IsExport, "var", s.Decls) 4285 } 4286 4287 case *js_ast.SIf: 4288 p.addSourceMapping(stmt.Loc) 4289 p.printIndent() 4290 p.printIf(s) 4291 4292 case *js_ast.SDoWhile: 4293 p.addSourceMapping(stmt.Loc) 4294 p.printIndent() 4295 p.printSpaceBeforeIdentifier() 4296 p.print("do") 4297 if block, ok := s.Body.Data.(*js_ast.SBlock); ok { 4298 p.printSpace() 4299 p.printBlock(s.Body.Loc, *block) 4300 p.printSpace() 4301 } else { 4302 p.printNewline() 4303 p.options.Indent++ 4304 p.printStmt(s.Body, 0) 4305 p.printSemicolonIfNeeded() 4306 p.options.Indent-- 4307 p.printIndent() 4308 } 4309 p.print("while") 4310 p.printSpace() 4311 p.print("(") 4312 if p.willPrintExprCommentsAtLoc(s.Test.Loc) { 4313 p.printNewline() 4314 p.options.Indent++ 4315 p.printIndent() 4316 p.printExpr(s.Test, js_ast.LLowest, 0) 4317 p.printNewline() 4318 p.options.Indent-- 4319 p.printIndent() 4320 } else { 4321 p.printExpr(s.Test, js_ast.LLowest, 0) 4322 } 4323 p.print(")") 4324 p.printSemicolonAfterStatement() 4325 4326 case *js_ast.SForIn: 4327 p.addSourceMapping(stmt.Loc) 4328 p.printIndent() 4329 p.printSpaceBeforeIdentifier() 4330 p.print("for") 4331 p.printSpace() 4332 p.print("(") 4333 hasInitComment := p.willPrintExprCommentsAtLoc(s.Init.Loc) 4334 hasValueComment := p.willPrintExprCommentsAtLoc(s.Value.Loc) 4335 if hasInitComment || hasValueComment { 4336 p.printNewline() 4337 p.options.Indent++ 4338 p.printIndent() 4339 } 4340 p.printForLoopInit(s.Init, forbidIn) 4341 p.printSpace() 4342 p.printSpaceBeforeIdentifier() 4343 p.print("in") 4344 if hasValueComment { 4345 p.printNewline() 4346 p.printIndent() 4347 } else { 4348 p.printSpace() 4349 } 4350 p.printExpr(s.Value, js_ast.LLowest, 0) 4351 if hasInitComment || hasValueComment { 4352 p.printNewline() 4353 p.options.Indent-- 4354 p.printIndent() 4355 } 4356 p.print(")") 4357 p.printBody(s.Body, s.IsSingleLineBody) 4358 4359 case *js_ast.SForOf: 4360 p.addSourceMapping(stmt.Loc) 4361 p.printIndent() 4362 p.printSpaceBeforeIdentifier() 4363 p.print("for") 4364 if s.Await.Len > 0 { 4365 p.print(" await") 4366 } 4367 p.printSpace() 4368 p.print("(") 4369 hasInitComment := p.willPrintExprCommentsAtLoc(s.Init.Loc) 4370 hasValueComment := p.willPrintExprCommentsAtLoc(s.Value.Loc) 4371 flags := forbidIn | isFollowedByOf 4372 if s.Await.Len > 0 { 4373 flags |= isInsideForAwait 4374 } 4375 if hasInitComment || hasValueComment { 4376 p.printNewline() 4377 p.options.Indent++ 4378 p.printIndent() 4379 } 4380 p.forOfInitStart = len(p.js) 4381 p.printForLoopInit(s.Init, flags) 4382 p.printSpace() 4383 p.printSpaceBeforeIdentifier() 4384 p.print("of") 4385 if hasValueComment { 4386 p.printNewline() 4387 p.printIndent() 4388 } else { 4389 p.printSpace() 4390 } 4391 p.printExpr(s.Value, js_ast.LComma, 0) 4392 if hasInitComment || hasValueComment { 4393 p.printNewline() 4394 p.options.Indent-- 4395 p.printIndent() 4396 } 4397 p.print(")") 4398 p.printBody(s.Body, s.IsSingleLineBody) 4399 4400 case *js_ast.SWhile: 4401 p.addSourceMapping(stmt.Loc) 4402 p.printIndent() 4403 p.printSpaceBeforeIdentifier() 4404 p.print("while") 4405 p.printSpace() 4406 p.print("(") 4407 if p.willPrintExprCommentsAtLoc(s.Test.Loc) { 4408 p.printNewline() 4409 p.options.Indent++ 4410 p.printIndent() 4411 p.printExpr(s.Test, js_ast.LLowest, 0) 4412 p.printNewline() 4413 p.options.Indent-- 4414 p.printIndent() 4415 } else { 4416 p.printExpr(s.Test, js_ast.LLowest, 0) 4417 } 4418 p.print(")") 4419 p.printBody(s.Body, s.IsSingleLineBody) 4420 4421 case *js_ast.SWith: 4422 p.addSourceMapping(stmt.Loc) 4423 p.printIndent() 4424 p.printSpaceBeforeIdentifier() 4425 p.print("with") 4426 p.printSpace() 4427 p.print("(") 4428 if p.willPrintExprCommentsAtLoc(s.Value.Loc) { 4429 p.printNewline() 4430 p.options.Indent++ 4431 p.printIndent() 4432 p.printExpr(s.Value, js_ast.LLowest, 0) 4433 p.printNewline() 4434 p.options.Indent-- 4435 p.printIndent() 4436 } else { 4437 p.printExpr(s.Value, js_ast.LLowest, 0) 4438 } 4439 p.print(")") 4440 p.withNesting++ 4441 p.printBody(s.Body, s.IsSingleLineBody) 4442 p.withNesting-- 4443 4444 case *js_ast.SLabel: 4445 // Avoid printing a source mapping that masks the one from the label 4446 if !p.options.MinifyWhitespace && (p.options.Indent > 0 || p.printNextIndentAsSpace) { 4447 p.addSourceMapping(stmt.Loc) 4448 p.printIndent() 4449 } 4450 4451 p.printSpaceBeforeIdentifier() 4452 name := p.renamer.NameForSymbol(s.Name.Ref) 4453 p.addSourceMappingForName(s.Name.Loc, name, s.Name.Ref) 4454 p.printIdentifier(name) 4455 p.print(":") 4456 p.printBody(s.Stmt, s.IsSingleLineStmt) 4457 4458 case *js_ast.STry: 4459 p.addSourceMapping(stmt.Loc) 4460 p.printIndent() 4461 p.printSpaceBeforeIdentifier() 4462 p.print("try") 4463 p.printSpace() 4464 p.printBlock(s.BlockLoc, s.Block) 4465 4466 if s.Catch != nil { 4467 p.printSpace() 4468 p.print("catch") 4469 if s.Catch.BindingOrNil.Data != nil { 4470 p.printSpace() 4471 p.print("(") 4472 p.printBinding(s.Catch.BindingOrNil) 4473 p.print(")") 4474 } 4475 p.printSpace() 4476 p.printBlock(s.Catch.BlockLoc, s.Catch.Block) 4477 } 4478 4479 if s.Finally != nil { 4480 p.printSpace() 4481 p.print("finally") 4482 p.printSpace() 4483 p.printBlock(s.Finally.Loc, s.Finally.Block) 4484 } 4485 4486 p.printNewline() 4487 4488 case *js_ast.SFor: 4489 init := s.InitOrNil 4490 update := s.UpdateOrNil 4491 4492 // Omit calls to empty functions from the output completely 4493 if p.options.MinifySyntax { 4494 if expr, ok := init.Data.(*js_ast.SExpr); ok { 4495 if value := p.simplifyUnusedExpr(expr.Value); value.Data == nil { 4496 init.Data = nil 4497 } else if value.Data != expr.Value.Data { 4498 init.Data = &js_ast.SExpr{Value: value} 4499 } 4500 } 4501 if update.Data != nil { 4502 update = p.simplifyUnusedExpr(update) 4503 } 4504 } 4505 4506 p.addSourceMapping(stmt.Loc) 4507 p.printIndent() 4508 p.printSpaceBeforeIdentifier() 4509 p.print("for") 4510 p.printSpace() 4511 p.print("(") 4512 isMultiLine := 4513 (init.Data != nil && p.willPrintExprCommentsAtLoc(init.Loc)) || 4514 (s.TestOrNil.Data != nil && p.willPrintExprCommentsAtLoc(s.TestOrNil.Loc)) || 4515 (update.Data != nil && p.willPrintExprCommentsAtLoc(update.Loc)) 4516 if isMultiLine { 4517 p.printNewline() 4518 p.options.Indent++ 4519 p.printIndent() 4520 } 4521 if init.Data != nil { 4522 p.printForLoopInit(init, forbidIn) 4523 } 4524 p.print(";") 4525 if isMultiLine { 4526 p.printNewline() 4527 p.printIndent() 4528 } else { 4529 p.printSpace() 4530 } 4531 if s.TestOrNil.Data != nil { 4532 p.printExpr(s.TestOrNil, js_ast.LLowest, 0) 4533 } 4534 p.print(";") 4535 if !isMultiLine { 4536 p.printSpace() 4537 } else if update.Data != nil { 4538 p.printNewline() 4539 p.printIndent() 4540 } 4541 if update.Data != nil { 4542 p.printExpr(update, js_ast.LLowest, exprResultIsUnused) 4543 } 4544 if isMultiLine { 4545 p.printNewline() 4546 p.options.Indent-- 4547 p.printIndent() 4548 } 4549 p.print(")") 4550 p.printBody(s.Body, s.IsSingleLineBody) 4551 4552 case *js_ast.SSwitch: 4553 p.addSourceMapping(stmt.Loc) 4554 p.printIndent() 4555 p.printSpaceBeforeIdentifier() 4556 p.print("switch") 4557 p.printSpace() 4558 p.print("(") 4559 if p.willPrintExprCommentsAtLoc(s.Test.Loc) { 4560 p.printNewline() 4561 p.options.Indent++ 4562 p.printIndent() 4563 p.printExpr(s.Test, js_ast.LLowest, 0) 4564 p.printNewline() 4565 p.options.Indent-- 4566 p.printIndent() 4567 } else { 4568 p.printExpr(s.Test, js_ast.LLowest, 0) 4569 } 4570 p.print(")") 4571 p.printSpace() 4572 p.addSourceMapping(s.BodyLoc) 4573 p.print("{") 4574 p.printNewline() 4575 p.options.Indent++ 4576 4577 for _, c := range s.Cases { 4578 p.printSemicolonIfNeeded() 4579 p.printIndent() 4580 p.addSourceMapping(c.Loc) 4581 4582 if c.ValueOrNil.Data != nil { 4583 p.print("case") 4584 p.printSpace() 4585 p.printExpr(c.ValueOrNil, js_ast.LLogicalAnd, 0) 4586 } else { 4587 p.print("default") 4588 } 4589 p.print(":") 4590 4591 if len(c.Body) == 1 { 4592 if block, ok := c.Body[0].Data.(*js_ast.SBlock); ok { 4593 p.printSpace() 4594 p.printBlock(c.Body[0].Loc, *block) 4595 p.printNewline() 4596 continue 4597 } 4598 } 4599 4600 p.printNewline() 4601 p.options.Indent++ 4602 for _, stmt := range c.Body { 4603 p.printSemicolonIfNeeded() 4604 p.printStmt(stmt, canOmitStatement) 4605 } 4606 p.options.Indent-- 4607 } 4608 4609 p.options.Indent-- 4610 p.printIndent() 4611 p.addSourceMapping(s.CloseBraceLoc) 4612 p.print("}") 4613 p.printNewline() 4614 p.needsSemicolon = false 4615 4616 case *js_ast.SImport: 4617 itemCount := 0 4618 4619 p.addSourceMapping(stmt.Loc) 4620 p.printIndent() 4621 p.printSpaceBeforeIdentifier() 4622 p.print("import") 4623 p.printSpace() 4624 4625 if s.DefaultName != nil { 4626 p.printSpaceBeforeIdentifier() 4627 name := p.renamer.NameForSymbol(s.DefaultName.Ref) 4628 p.addSourceMappingForName(s.DefaultName.Loc, name, s.DefaultName.Ref) 4629 p.printIdentifier(name) 4630 itemCount++ 4631 } 4632 4633 if s.Items != nil { 4634 if itemCount > 0 { 4635 p.print(",") 4636 p.printSpace() 4637 } 4638 4639 p.print("{") 4640 if !s.IsSingleLine { 4641 p.options.Indent++ 4642 } 4643 4644 for i, item := range *s.Items { 4645 if i != 0 { 4646 p.print(",") 4647 } 4648 4649 if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() { 4650 if s.IsSingleLine { 4651 p.printSpace() 4652 } else { 4653 p.printNewline() 4654 p.printIndent() 4655 } 4656 } 4657 4658 p.printClauseAlias(item.AliasLoc, item.Alias) 4659 4660 name := p.renamer.NameForSymbol(item.Name.Ref) 4661 if name != item.Alias { 4662 p.printSpace() 4663 p.printSpaceBeforeIdentifier() 4664 p.print("as ") 4665 p.addSourceMappingForName(item.Name.Loc, name, item.Name.Ref) 4666 p.printIdentifier(name) 4667 } 4668 } 4669 4670 if !s.IsSingleLine { 4671 p.options.Indent-- 4672 p.printNewline() 4673 p.printIndent() 4674 } else if len(*s.Items) > 0 { 4675 p.printSpace() 4676 } 4677 4678 p.print("}") 4679 itemCount++ 4680 } 4681 4682 if s.StarNameLoc != nil { 4683 if itemCount > 0 { 4684 p.print(",") 4685 p.printSpace() 4686 } 4687 4688 p.print("*") 4689 p.printSpace() 4690 p.print("as ") 4691 name := p.renamer.NameForSymbol(s.NamespaceRef) 4692 p.addSourceMappingForName(*s.StarNameLoc, name, s.NamespaceRef) 4693 p.printIdentifier(name) 4694 itemCount++ 4695 } 4696 4697 if itemCount > 0 { 4698 p.printSpace() 4699 p.printSpaceBeforeIdentifier() 4700 p.print("from") 4701 p.printSpace() 4702 } 4703 4704 p.printPath(s.ImportRecordIndex, ast.ImportStmt) 4705 p.printSemicolonAfterStatement() 4706 4707 case *js_ast.SBlock: 4708 p.addSourceMapping(stmt.Loc) 4709 p.printIndent() 4710 p.printBlock(stmt.Loc, *s) 4711 p.printNewline() 4712 4713 case *js_ast.SDebugger: 4714 p.addSourceMapping(stmt.Loc) 4715 p.printIndent() 4716 p.printSpaceBeforeIdentifier() 4717 p.print("debugger") 4718 p.printSemicolonAfterStatement() 4719 4720 case *js_ast.SDirective: 4721 p.addSourceMapping(stmt.Loc) 4722 p.printIndent() 4723 p.printSpaceBeforeIdentifier() 4724 p.printQuotedUTF16(s.Value, 0) 4725 p.printSemicolonAfterStatement() 4726 4727 case *js_ast.SBreak: 4728 p.addSourceMapping(stmt.Loc) 4729 p.printIndent() 4730 p.printSpaceBeforeIdentifier() 4731 p.print("break") 4732 if s.Label != nil { 4733 p.print(" ") 4734 name := p.renamer.NameForSymbol(s.Label.Ref) 4735 p.addSourceMappingForName(s.Label.Loc, name, s.Label.Ref) 4736 p.printIdentifier(name) 4737 } 4738 p.printSemicolonAfterStatement() 4739 4740 case *js_ast.SContinue: 4741 p.addSourceMapping(stmt.Loc) 4742 p.printIndent() 4743 p.printSpaceBeforeIdentifier() 4744 p.print("continue") 4745 if s.Label != nil { 4746 p.print(" ") 4747 name := p.renamer.NameForSymbol(s.Label.Ref) 4748 p.addSourceMappingForName(s.Label.Loc, name, s.Label.Ref) 4749 p.printIdentifier(name) 4750 } 4751 p.printSemicolonAfterStatement() 4752 4753 case *js_ast.SReturn: 4754 p.addSourceMapping(stmt.Loc) 4755 p.printIndent() 4756 p.printSpaceBeforeIdentifier() 4757 p.print("return") 4758 if s.ValueOrNil.Data != nil { 4759 p.printSpace() 4760 p.printExprWithoutLeadingNewline(s.ValueOrNil, js_ast.LLowest, 0) 4761 } 4762 p.printSemicolonAfterStatement() 4763 4764 case *js_ast.SThrow: 4765 p.addSourceMapping(stmt.Loc) 4766 p.printIndent() 4767 p.printSpaceBeforeIdentifier() 4768 p.print("throw") 4769 p.printSpace() 4770 p.printExprWithoutLeadingNewline(s.Value, js_ast.LLowest, 0) 4771 p.printSemicolonAfterStatement() 4772 4773 case *js_ast.SExpr: 4774 value := s.Value 4775 4776 // Omit calls to empty functions from the output completely 4777 if p.options.MinifySyntax { 4778 value = p.simplifyUnusedExpr(value) 4779 if value.Data == nil { 4780 // If this statement is not in a block, then we still need to emit something 4781 if (flags & canOmitStatement) == 0 { 4782 // "if (x) empty();" => "if (x) ;" 4783 p.addSourceMapping(stmt.Loc) 4784 p.printIndent() 4785 p.print(";") 4786 p.printNewline() 4787 } else { 4788 // "if (x) { empty(); }" => "if (x) {}" 4789 } 4790 break 4791 } 4792 } 4793 4794 // Avoid printing a source mapping when the expression would print one in 4795 // the same spot. We don't want to accidentally mask the mapping it emits. 4796 if !p.options.MinifyWhitespace && (p.options.Indent > 0 || p.printNextIndentAsSpace) { 4797 p.addSourceMapping(stmt.Loc) 4798 p.printIndent() 4799 } 4800 4801 p.stmtStart = len(p.js) 4802 p.printExpr(value, js_ast.LLowest, exprResultIsUnused) 4803 p.printSemicolonAfterStatement() 4804 4805 default: 4806 panic(fmt.Sprintf("Unexpected statement of type %T", stmt.Data)) 4807 } 4808 } 4809 4810 type Options struct { 4811 RequireOrImportMetaForSource func(uint32) RequireOrImportMeta 4812 4813 // Cross-module inlining of TypeScript enums is actually done during printing 4814 TSEnums map[ast.Ref]map[string]js_ast.TSEnumValue 4815 4816 // Cross-module inlining of detected inlinable constants is also done during printing 4817 ConstValues map[ast.Ref]js_ast.ConstValue 4818 4819 // Property mangling results go here 4820 MangledProps map[ast.Ref]string 4821 4822 // This will be present if the input file had a source map. In that case we 4823 // want to map all the way back to the original input file(s). 4824 InputSourceMap *sourcemap.SourceMap 4825 4826 // If we're writing out a source map, this table of line start indices lets 4827 // us do binary search on to figure out what line a given AST node came from 4828 LineOffsetTables []sourcemap.LineOffsetTable 4829 4830 ToCommonJSRef ast.Ref 4831 ToESMRef ast.Ref 4832 RuntimeRequireRef ast.Ref 4833 UnsupportedFeatures compat.JSFeature 4834 Indent int 4835 LineLimit int 4836 OutputFormat config.Format 4837 MinifyWhitespace bool 4838 MinifyIdentifiers bool 4839 MinifySyntax bool 4840 ASCIIOnly bool 4841 LegalComments config.LegalComments 4842 SourceMap config.SourceMap 4843 AddSourceMappings bool 4844 NeedsMetafile bool 4845 } 4846 4847 type RequireOrImportMeta struct { 4848 // CommonJS files will return the "require_*" wrapper function and an invalid 4849 // exports object reference. Lazily-initialized ESM files will return the 4850 // "init_*" wrapper function and the exports object for that file. 4851 WrapperRef ast.Ref 4852 ExportsRef ast.Ref 4853 IsWrapperAsync bool 4854 } 4855 4856 type PrintResult struct { 4857 JS []byte 4858 ExtractedLegalComments []string 4859 JSONMetadataImports []string 4860 4861 // This source map chunk just contains the VLQ-encoded offsets for the "JS" 4862 // field above. It's not a full source map. The bundler will be joining many 4863 // source map chunks together to form the final source map. 4864 SourceMapChunk sourcemap.Chunk 4865 } 4866 4867 func Print(tree js_ast.AST, symbols ast.SymbolMap, r renamer.Renamer, options Options) PrintResult { 4868 p := &printer{ 4869 symbols: symbols, 4870 renamer: r, 4871 importRecords: tree.ImportRecords, 4872 options: options, 4873 moduleType: tree.ModuleTypeData.Type, 4874 exprComments: tree.ExprComments, 4875 wasLazyExport: tree.HasLazyExport, 4876 4877 stmtStart: -1, 4878 exportDefaultStart: -1, 4879 arrowExprStart: -1, 4880 forOfInitStart: -1, 4881 4882 prevOpEnd: -1, 4883 needSpaceBeforeDot: -1, 4884 prevRegExpEnd: -1, 4885 noLeadingNewlineHere: -1, 4886 builder: sourcemap.MakeChunkBuilder(options.InputSourceMap, options.LineOffsetTables, options.ASCIIOnly), 4887 } 4888 4889 if p.exprComments != nil { 4890 p.printedExprComments = make(map[logger.Loc]bool) 4891 } 4892 4893 p.astHelpers = js_ast.MakeHelperContext(func(ref ast.Ref) bool { 4894 ref = ast.FollowSymbols(symbols, ref) 4895 return symbols.Get(ref).Kind == ast.SymbolUnbound 4896 }) 4897 4898 // Add the top-level directive if present 4899 for _, directive := range tree.Directives { 4900 p.printIndent() 4901 p.printQuotedUTF8(directive, 0) 4902 p.print(";") 4903 p.printNewline() 4904 } 4905 4906 for _, part := range tree.Parts { 4907 for _, stmt := range part.Stmts { 4908 p.printStmt(stmt, canOmitStatement) 4909 p.printSemicolonIfNeeded() 4910 } 4911 } 4912 4913 result := PrintResult{ 4914 JS: p.js, 4915 JSONMetadataImports: p.jsonMetadataImports, 4916 ExtractedLegalComments: p.extractedLegalComments, 4917 } 4918 if options.SourceMap != config.SourceMapNone { 4919 // This is expensive. Only do this if it's necessary. 4920 result.SourceMapChunk = p.builder.GenerateChunk(p.js) 4921 } 4922 return result 4923 }