mirror of
https://github.com/hanzoai/esbuild.git
synced 2026-09-24 16:58:50 +00:00
5026 lines
132 KiB
Go
5026 lines
132 KiB
Go
package js_printer
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"math"
|
|
"math/big"
|
|
"strconv"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"github.com/evanw/esbuild/internal/ast"
|
|
"github.com/evanw/esbuild/internal/compat"
|
|
"github.com/evanw/esbuild/internal/config"
|
|
"github.com/evanw/esbuild/internal/helpers"
|
|
"github.com/evanw/esbuild/internal/js_ast"
|
|
"github.com/evanw/esbuild/internal/logger"
|
|
"github.com/evanw/esbuild/internal/renamer"
|
|
"github.com/evanw/esbuild/internal/sourcemap"
|
|
)
|
|
|
|
var positiveInfinity = math.Inf(1)
|
|
var negativeInfinity = math.Inf(-1)
|
|
|
|
const hexChars = "0123456789ABCDEF"
|
|
const firstASCII = 0x20
|
|
const lastASCII = 0x7E
|
|
const firstHighSurrogate = 0xD800
|
|
const lastHighSurrogate = 0xDBFF
|
|
const firstLowSurrogate = 0xDC00
|
|
const lastLowSurrogate = 0xDFFF
|
|
|
|
func QuoteIdentifier(js []byte, name string, unsupportedFeatures compat.JSFeature) []byte {
|
|
isASCII := false
|
|
asciiStart := 0
|
|
for i, c := range name {
|
|
if c >= firstASCII && c <= lastASCII {
|
|
// Fast path: a run of ASCII characters
|
|
if !isASCII {
|
|
isASCII = true
|
|
asciiStart = i
|
|
}
|
|
} else {
|
|
// Slow path: escape non-ACSII characters
|
|
if isASCII {
|
|
js = append(js, name[asciiStart:i]...)
|
|
isASCII = false
|
|
}
|
|
if c <= 0xFFFF {
|
|
js = append(js, '\\', 'u', hexChars[c>>12], hexChars[(c>>8)&15], hexChars[(c>>4)&15], hexChars[c&15])
|
|
} else if !unsupportedFeatures.Has(compat.UnicodeEscapes) {
|
|
js = append(js, fmt.Sprintf("\\u{%X}", c)...)
|
|
} else {
|
|
panic("Internal error: Cannot encode identifier: Unicode escapes are unsupported")
|
|
}
|
|
}
|
|
}
|
|
if isASCII {
|
|
// Print one final run of ASCII characters
|
|
js = append(js, name[asciiStart:]...)
|
|
}
|
|
return js
|
|
}
|
|
|
|
func (p *printer) printUnquotedUTF16(text []uint16, quote rune, flags printQuotedFlags) {
|
|
temp := make([]byte, utf8.UTFMax)
|
|
js := p.js
|
|
i := 0
|
|
n := len(text)
|
|
|
|
// Only compute the line length if necessary
|
|
var startLineLength int
|
|
wrapLongLines := false
|
|
if p.options.LineLimit > 0 && (flags&printQuotedNoWrap) == 0 {
|
|
startLineLength = p.currentLineLength()
|
|
if startLineLength > p.options.LineLimit {
|
|
startLineLength = p.options.LineLimit
|
|
}
|
|
wrapLongLines = true
|
|
}
|
|
|
|
for i < n {
|
|
// Wrap long lines that are over the limit using escaped newlines
|
|
if wrapLongLines && startLineLength+i >= p.options.LineLimit {
|
|
js = append(js, "\\\n"...)
|
|
startLineLength -= p.options.LineLimit
|
|
}
|
|
|
|
c := text[i]
|
|
i++
|
|
|
|
switch c {
|
|
// Special-case the null character since it may mess with code written in C
|
|
// that treats null characters as the end of the string.
|
|
case '\x00':
|
|
// We don't want "\x001" to be written as "\01"
|
|
if i < n && text[i] >= '0' && text[i] <= '9' {
|
|
js = append(js, "\\x00"...)
|
|
} else {
|
|
js = append(js, "\\0"...)
|
|
}
|
|
|
|
// Special-case the bell character since it may cause dumping this file to
|
|
// the terminal to make a sound, which is undesirable. Note that we can't
|
|
// use an octal literal to print this shorter since octal literals are not
|
|
// allowed in strict mode (or in template strings).
|
|
case '\x07':
|
|
js = append(js, "\\x07"...)
|
|
|
|
case '\b':
|
|
js = append(js, "\\b"...)
|
|
|
|
case '\f':
|
|
js = append(js, "\\f"...)
|
|
|
|
case '\n':
|
|
if quote == '`' {
|
|
startLineLength = -i // Printing a real newline resets the line length
|
|
js = append(js, '\n')
|
|
} else {
|
|
js = append(js, "\\n"...)
|
|
}
|
|
|
|
case '\r':
|
|
js = append(js, "\\r"...)
|
|
|
|
case '\v':
|
|
js = append(js, "\\v"...)
|
|
|
|
case '\x1B':
|
|
js = append(js, "\\x1B"...)
|
|
|
|
case '\\':
|
|
js = append(js, "\\\\"...)
|
|
|
|
case '/':
|
|
// Avoid generating the sequence "</script" in JS code
|
|
if !p.options.UnsupportedFeatures.Has(compat.InlineScript) && i >= 2 && text[i-2] == '<' && i+6 <= len(text) {
|
|
script := "script"
|
|
matches := true
|
|
for j := 0; j < 6; j++ {
|
|
a := text[i+j]
|
|
b := uint16(script[j])
|
|
if a >= 'A' && a <= 'Z' {
|
|
a += 'a' - 'A'
|
|
}
|
|
if a != b {
|
|
matches = false
|
|
break
|
|
}
|
|
}
|
|
if matches {
|
|
js = append(js, '\\')
|
|
}
|
|
}
|
|
js = append(js, '/')
|
|
|
|
case '\'':
|
|
if quote == '\'' {
|
|
js = append(js, '\\')
|
|
}
|
|
js = append(js, '\'')
|
|
|
|
case '"':
|
|
if quote == '"' {
|
|
js = append(js, '\\')
|
|
}
|
|
js = append(js, '"')
|
|
|
|
case '`':
|
|
if quote == '`' {
|
|
js = append(js, '\\')
|
|
}
|
|
js = append(js, '`')
|
|
|
|
case '$':
|
|
if quote == '`' && i < n && text[i] == '{' {
|
|
js = append(js, '\\')
|
|
}
|
|
js = append(js, '$')
|
|
|
|
case '\u2028':
|
|
js = append(js, "\\u2028"...)
|
|
|
|
case '\u2029':
|
|
js = append(js, "\\u2029"...)
|
|
|
|
case '\uFEFF':
|
|
js = append(js, "\\uFEFF"...)
|
|
|
|
default:
|
|
switch {
|
|
// Common case: just append a single byte
|
|
case c <= lastASCII:
|
|
js = append(js, byte(c))
|
|
|
|
// Is this a high surrogate?
|
|
case c >= firstHighSurrogate && c <= lastHighSurrogate:
|
|
// Is there a next character?
|
|
if i < n {
|
|
c2 := text[i]
|
|
|
|
// Is it a low surrogate?
|
|
if c2 >= firstLowSurrogate && c2 <= lastLowSurrogate {
|
|
r := (rune(c) << 10) + rune(c2) + (0x10000 - (firstHighSurrogate << 10) - firstLowSurrogate)
|
|
i++
|
|
|
|
// Escape this character if UTF-8 isn't allowed
|
|
if p.options.ASCIIOnly {
|
|
if !p.options.UnsupportedFeatures.Has(compat.UnicodeEscapes) {
|
|
js = append(js, fmt.Sprintf("\\u{%X}", r)...)
|
|
} else {
|
|
js = append(js,
|
|
'\\', 'u', hexChars[c>>12], hexChars[(c>>8)&15], hexChars[(c>>4)&15], hexChars[c&15],
|
|
'\\', 'u', hexChars[c2>>12], hexChars[(c2>>8)&15], hexChars[(c2>>4)&15], hexChars[c2&15],
|
|
)
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Otherwise, encode to UTF-8
|
|
width := utf8.EncodeRune(temp, r)
|
|
js = append(js, temp[:width]...)
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Write an unpaired high surrogate
|
|
js = append(js, '\\', 'u', hexChars[c>>12], hexChars[(c>>8)&15], hexChars[(c>>4)&15], hexChars[c&15])
|
|
|
|
// Is this an unpaired low surrogate or four-digit hex escape?
|
|
case (c >= firstLowSurrogate && c <= lastLowSurrogate) || (p.options.ASCIIOnly && c > 0xFF):
|
|
js = append(js, '\\', 'u', hexChars[c>>12], hexChars[(c>>8)&15], hexChars[(c>>4)&15], hexChars[c&15])
|
|
|
|
// Can this be a two-digit hex escape?
|
|
case p.options.ASCIIOnly:
|
|
js = append(js, '\\', 'x', hexChars[c>>4], hexChars[c&15])
|
|
|
|
// Otherwise, just encode to UTF-8
|
|
default:
|
|
width := utf8.EncodeRune(temp, rune(c))
|
|
js = append(js, temp[:width]...)
|
|
}
|
|
}
|
|
}
|
|
|
|
p.js = js
|
|
}
|
|
|
|
// JSX tag syntax doesn't support character escapes so non-ASCII identifiers
|
|
// must be printed as UTF-8 even when the charset is set to ASCII.
|
|
func (p *printer) printJSXTag(tagOrNil js_ast.Expr) {
|
|
switch e := tagOrNil.Data.(type) {
|
|
case *js_ast.EString:
|
|
p.addSourceMapping(tagOrNil.Loc)
|
|
p.print(helpers.UTF16ToString(e.Value))
|
|
|
|
case *js_ast.EIdentifier:
|
|
name := p.renamer.NameForSymbol(e.Ref)
|
|
p.addSourceMappingForName(tagOrNil.Loc, name, e.Ref)
|
|
p.print(name)
|
|
|
|
case *js_ast.EDot:
|
|
p.printJSXTag(e.Target)
|
|
p.print(".")
|
|
p.addSourceMapping(e.NameLoc)
|
|
p.print(e.Name)
|
|
|
|
default:
|
|
if tagOrNil.Data != nil {
|
|
p.printExpr(tagOrNil, js_ast.LLowest, 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
type printer struct {
|
|
symbols ast.SymbolMap
|
|
astHelpers js_ast.HelperContext
|
|
renamer renamer.Renamer
|
|
importRecords []ast.ImportRecord
|
|
callTarget js_ast.E
|
|
exprComments map[logger.Loc][]string
|
|
printedExprComments map[logger.Loc]bool
|
|
hasLegalComment map[string]struct{}
|
|
extractedLegalComments []string
|
|
js []byte
|
|
jsonMetadataImports []string
|
|
binaryExprStack []binaryExprVisitor
|
|
options Options
|
|
builder sourcemap.ChunkBuilder
|
|
printNextIndentAsSpace bool
|
|
|
|
stmtStart int
|
|
exportDefaultStart int
|
|
arrowExprStart int
|
|
forOfInitStart int
|
|
|
|
withNesting int
|
|
prevOpEnd int
|
|
needSpaceBeforeDot int
|
|
prevRegExpEnd int
|
|
noLeadingNewlineHere int
|
|
oldLineStart int
|
|
oldLineEnd int
|
|
intToBytesBuffer [64]byte
|
|
needsSemicolon bool
|
|
wasLazyExport bool
|
|
prevOp js_ast.OpCode
|
|
moduleType js_ast.ModuleType
|
|
}
|
|
|
|
func (p *printer) print(text string) {
|
|
p.js = append(p.js, text...)
|
|
}
|
|
|
|
// This is the same as "print(string(bytes))" without any unnecessary temporary
|
|
// allocations
|
|
func (p *printer) printBytes(bytes []byte) {
|
|
p.js = append(p.js, bytes...)
|
|
}
|
|
|
|
type printQuotedFlags uint8
|
|
|
|
const (
|
|
printQuotedAllowBacktick printQuotedFlags = 1 << iota
|
|
printQuotedNoWrap
|
|
)
|
|
|
|
func (p *printer) printQuotedUTF8(text string, flags printQuotedFlags) {
|
|
p.printQuotedUTF16(helpers.StringToUTF16(text), flags)
|
|
}
|
|
|
|
func (p *printer) addSourceMapping(loc logger.Loc) {
|
|
if p.options.AddSourceMappings {
|
|
p.builder.AddSourceMapping(loc, "", p.js)
|
|
}
|
|
}
|
|
|
|
func (p *printer) addSourceMappingForName(loc logger.Loc, name string, ref ast.Ref) {
|
|
if p.options.AddSourceMappings {
|
|
if originalName := p.symbols.Get(ast.FollowSymbols(p.symbols, ref)).OriginalName; originalName != name {
|
|
p.builder.AddSourceMapping(loc, originalName, p.js)
|
|
} else {
|
|
p.builder.AddSourceMapping(loc, "", p.js)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *printer) printIndent() {
|
|
if p.options.MinifyWhitespace {
|
|
return
|
|
}
|
|
|
|
if p.printNextIndentAsSpace {
|
|
p.print(" ")
|
|
p.printNextIndentAsSpace = false
|
|
return
|
|
}
|
|
|
|
indent := p.options.Indent
|
|
if p.options.LineLimit > 0 && indent*2 >= p.options.LineLimit {
|
|
indent = p.options.LineLimit / 2
|
|
}
|
|
for i := 0; i < indent; i++ {
|
|
p.print(" ")
|
|
}
|
|
}
|
|
|
|
func (p *printer) mangledPropName(ref ast.Ref) string {
|
|
ref = ast.FollowSymbols(p.symbols, ref)
|
|
if name, ok := p.options.MangledProps[ref]; ok {
|
|
return name
|
|
}
|
|
return p.renamer.NameForSymbol(ref)
|
|
}
|
|
|
|
func (p *printer) tryToGetImportedEnumValue(target js_ast.Expr, name string) (js_ast.TSEnumValue, bool) {
|
|
if id, ok := target.Data.(*js_ast.EImportIdentifier); ok {
|
|
ref := ast.FollowSymbols(p.symbols, id.Ref)
|
|
if symbol := p.symbols.Get(ref); symbol.Kind == ast.SymbolTSEnum {
|
|
if enum, ok := p.options.TSEnums[ref]; ok {
|
|
value, ok := enum[name]
|
|
return value, ok
|
|
}
|
|
}
|
|
}
|
|
return js_ast.TSEnumValue{}, false
|
|
}
|
|
|
|
func (p *printer) tryToGetImportedEnumValueUTF16(target js_ast.Expr, name []uint16) (js_ast.TSEnumValue, string, bool) {
|
|
if id, ok := target.Data.(*js_ast.EImportIdentifier); ok {
|
|
ref := ast.FollowSymbols(p.symbols, id.Ref)
|
|
if symbol := p.symbols.Get(ref); symbol.Kind == ast.SymbolTSEnum {
|
|
if enum, ok := p.options.TSEnums[ref]; ok {
|
|
name := helpers.UTF16ToString(name)
|
|
value, ok := enum[name]
|
|
return value, name, ok
|
|
}
|
|
}
|
|
}
|
|
return js_ast.TSEnumValue{}, "", false
|
|
}
|
|
|
|
func (p *printer) printClauseAlias(loc logger.Loc, alias string) {
|
|
if js_ast.IsIdentifier(alias) {
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(loc)
|
|
p.printIdentifier(alias)
|
|
} else {
|
|
p.addSourceMapping(loc)
|
|
p.printQuotedUTF8(alias, 0)
|
|
}
|
|
}
|
|
|
|
// Note: The functions below check whether something can be printed as an
|
|
// identifier or if it needs to be quoted (e.g. "x.y" vs. "x['y']") using the
|
|
// ES5 identifier validity test to maximize cross-platform portability. Even
|
|
// though newer JavaScript environments can handle more Unicode characters,
|
|
// there isn't a published document that says which Unicode versions are
|
|
// supported by which browsers. Even if a character is considered valid in the
|
|
// latest version of Unicode, we don't know if the browser we're targeting
|
|
// contains an older version of Unicode or not. So for safety, we quote
|
|
// anything that isn't guaranteed to be compatible with ES5, the oldest
|
|
// JavaScript language target that we support.
|
|
|
|
func CanEscapeIdentifier(name string, UnsupportedFeatures compat.JSFeature, asciiOnly bool) bool {
|
|
return js_ast.IsIdentifierES5AndESNext(name) && (!asciiOnly ||
|
|
!UnsupportedFeatures.Has(compat.UnicodeEscapes) ||
|
|
!helpers.ContainsNonBMPCodePoint(name))
|
|
}
|
|
|
|
func (p *printer) canPrintIdentifier(name string) bool {
|
|
return js_ast.IsIdentifierES5AndESNext(name) && (!p.options.ASCIIOnly ||
|
|
!p.options.UnsupportedFeatures.Has(compat.UnicodeEscapes) ||
|
|
!helpers.ContainsNonBMPCodePoint(name))
|
|
}
|
|
|
|
func (p *printer) canPrintIdentifierUTF16(name []uint16) bool {
|
|
return js_ast.IsIdentifierES5AndESNextUTF16(name) && (!p.options.ASCIIOnly ||
|
|
!p.options.UnsupportedFeatures.Has(compat.UnicodeEscapes) ||
|
|
!helpers.ContainsNonBMPCodePointUTF16(name))
|
|
}
|
|
|
|
func (p *printer) printIdentifier(name string) {
|
|
if p.options.ASCIIOnly {
|
|
p.js = QuoteIdentifier(p.js, name, p.options.UnsupportedFeatures)
|
|
} else {
|
|
p.print(name)
|
|
}
|
|
}
|
|
|
|
// This is the same as "printIdentifier(StringToUTF16(bytes))" without any
|
|
// unnecessary temporary allocations
|
|
func (p *printer) printIdentifierUTF16(name []uint16) {
|
|
var temp [utf8.UTFMax]byte
|
|
n := len(name)
|
|
|
|
for i := 0; i < n; i++ {
|
|
c := rune(name[i])
|
|
|
|
if c >= firstHighSurrogate && c <= lastHighSurrogate && i+1 < n {
|
|
if c2 := rune(name[i+1]); c2 >= firstLowSurrogate && c2 <= lastLowSurrogate {
|
|
c = (c << 10) + c2 + (0x10000 - (firstHighSurrogate << 10) - firstLowSurrogate)
|
|
i++
|
|
}
|
|
}
|
|
|
|
if p.options.ASCIIOnly && c > lastASCII {
|
|
if c <= 0xFFFF {
|
|
p.js = append(p.js, '\\', 'u', hexChars[c>>12], hexChars[(c>>8)&15], hexChars[(c>>4)&15], hexChars[c&15])
|
|
} else if !p.options.UnsupportedFeatures.Has(compat.UnicodeEscapes) {
|
|
p.js = append(p.js, fmt.Sprintf("\\u{%X}", c)...)
|
|
} else {
|
|
panic("Internal error: Cannot encode identifier: Unicode escapes are unsupported")
|
|
}
|
|
continue
|
|
}
|
|
|
|
width := utf8.EncodeRune(temp[:], c)
|
|
p.js = append(p.js, temp[:width]...)
|
|
}
|
|
}
|
|
|
|
func (p *printer) printNumber(value float64, level js_ast.L) {
|
|
absValue := math.Abs(value)
|
|
|
|
if value != value {
|
|
p.printSpaceBeforeIdentifier()
|
|
if p.withNesting != 0 {
|
|
// "with (x) NaN" really means "x.NaN" so avoid identifiers when "with" is present
|
|
wrap := level >= js_ast.LMultiply
|
|
if wrap {
|
|
p.print("(")
|
|
}
|
|
if p.options.MinifyWhitespace {
|
|
p.print("0/0")
|
|
} else {
|
|
p.print("0 / 0")
|
|
}
|
|
if wrap {
|
|
p.print(")")
|
|
}
|
|
} else {
|
|
p.print("NaN")
|
|
}
|
|
} else if value == positiveInfinity || value == negativeInfinity {
|
|
// "with (x) Infinity" really means "x.Infinity" so avoid identifiers when "with" is present
|
|
wrap := ((p.options.MinifySyntax || p.withNesting != 0) && level >= js_ast.LMultiply) ||
|
|
(value == negativeInfinity && level >= js_ast.LPrefix)
|
|
if wrap {
|
|
p.print("(")
|
|
}
|
|
if value == negativeInfinity {
|
|
p.printSpaceBeforeOperator(js_ast.UnOpNeg)
|
|
p.print("-")
|
|
} else {
|
|
p.printSpaceBeforeIdentifier()
|
|
}
|
|
if !p.options.MinifySyntax && p.withNesting == 0 {
|
|
p.print("Infinity")
|
|
} else if p.options.MinifyWhitespace {
|
|
p.print("1/0")
|
|
} else {
|
|
p.print("1 / 0")
|
|
}
|
|
if wrap {
|
|
p.print(")")
|
|
}
|
|
} else {
|
|
if !math.Signbit(value) {
|
|
p.printSpaceBeforeIdentifier()
|
|
p.printNonNegativeFloat(absValue)
|
|
} else if level >= js_ast.LPrefix {
|
|
// Expressions such as "(-1).toString" need to wrap negative numbers.
|
|
// Instead of testing for "value < 0" we test for "signbit(value)" and
|
|
// "!isNaN(value)" because we need this to be true for "-0" and "-0 < 0"
|
|
// is false.
|
|
p.print("(-")
|
|
p.printNonNegativeFloat(absValue)
|
|
p.print(")")
|
|
} else {
|
|
p.printSpaceBeforeOperator(js_ast.UnOpNeg)
|
|
p.print("-")
|
|
p.printNonNegativeFloat(absValue)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *printer) willPrintExprCommentsAtLoc(loc logger.Loc) bool {
|
|
return !p.options.MinifyWhitespace && p.exprComments[loc] != nil && !p.printedExprComments[loc]
|
|
}
|
|
|
|
func (p *printer) willPrintExprCommentsForAnyOf(exprs []js_ast.Expr) bool {
|
|
for _, expr := range exprs {
|
|
if p.willPrintExprCommentsAtLoc(expr.Loc) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (p *printer) printBinding(binding js_ast.Binding) {
|
|
switch b := binding.Data.(type) {
|
|
case *js_ast.BMissing:
|
|
p.addSourceMapping(binding.Loc)
|
|
|
|
case *js_ast.BIdentifier:
|
|
name := p.renamer.NameForSymbol(b.Ref)
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMappingForName(binding.Loc, name, b.Ref)
|
|
p.printIdentifier(name)
|
|
|
|
case *js_ast.BArray:
|
|
isMultiLine := (len(b.Items) > 0 && !b.IsSingleLine) || p.willPrintExprCommentsAtLoc(b.CloseBracketLoc)
|
|
if !p.options.MinifyWhitespace && !isMultiLine {
|
|
for _, item := range b.Items {
|
|
if p.willPrintExprCommentsAtLoc(item.Loc) {
|
|
isMultiLine = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
p.addSourceMapping(binding.Loc)
|
|
p.print("[")
|
|
if len(b.Items) > 0 || isMultiLine {
|
|
if isMultiLine {
|
|
p.options.Indent++
|
|
}
|
|
|
|
for i, item := range b.Items {
|
|
if i != 0 {
|
|
p.print(",")
|
|
}
|
|
if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() {
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.printIndent()
|
|
} else if i != 0 {
|
|
p.printSpace()
|
|
}
|
|
}
|
|
p.printExprCommentsAtLoc(item.Loc)
|
|
if b.HasSpread && i+1 == len(b.Items) {
|
|
p.addSourceMapping(item.Loc)
|
|
p.print("...")
|
|
p.printExprCommentsAtLoc(item.Binding.Loc)
|
|
}
|
|
p.printBinding(item.Binding)
|
|
|
|
if item.DefaultValueOrNil.Data != nil {
|
|
p.printSpace()
|
|
p.print("=")
|
|
p.printSpace()
|
|
p.printExprWithoutLeadingNewline(item.DefaultValueOrNil, js_ast.LComma, 0)
|
|
}
|
|
|
|
// Make sure there's a comma after trailing missing items
|
|
if _, ok := item.Binding.Data.(*js_ast.BMissing); ok && i == len(b.Items)-1 {
|
|
p.print(",")
|
|
}
|
|
}
|
|
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.printExprCommentsAfterCloseTokenAtLoc(b.CloseBracketLoc)
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
}
|
|
}
|
|
p.addSourceMapping(b.CloseBracketLoc)
|
|
p.print("]")
|
|
|
|
case *js_ast.BObject:
|
|
isMultiLine := (len(b.Properties) > 0 && !b.IsSingleLine) || p.willPrintExprCommentsAtLoc(b.CloseBraceLoc)
|
|
if !p.options.MinifyWhitespace && !isMultiLine {
|
|
for _, property := range b.Properties {
|
|
if p.willPrintExprCommentsAtLoc(property.Loc) {
|
|
isMultiLine = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
p.addSourceMapping(binding.Loc)
|
|
p.print("{")
|
|
if len(b.Properties) > 0 || isMultiLine {
|
|
if isMultiLine {
|
|
p.options.Indent++
|
|
}
|
|
|
|
for i, property := range b.Properties {
|
|
if i != 0 {
|
|
p.print(",")
|
|
}
|
|
if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() {
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.printIndent()
|
|
} else {
|
|
p.printSpace()
|
|
}
|
|
}
|
|
|
|
p.printExprCommentsAtLoc(property.Loc)
|
|
|
|
if property.IsSpread {
|
|
p.addSourceMapping(property.Loc)
|
|
p.print("...")
|
|
p.printExprCommentsAtLoc(property.Value.Loc)
|
|
} else {
|
|
if property.IsComputed {
|
|
p.addSourceMapping(property.Loc)
|
|
isMultiLine := p.willPrintExprCommentsAtLoc(property.Key.Loc) || p.willPrintExprCommentsAtLoc(property.CloseBracketLoc)
|
|
p.print("[")
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
}
|
|
p.printExpr(property.Key, js_ast.LComma, 0)
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.printExprCommentsAfterCloseTokenAtLoc(property.CloseBracketLoc)
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
}
|
|
if property.CloseBracketLoc.Start > property.Loc.Start {
|
|
p.addSourceMapping(property.CloseBracketLoc)
|
|
}
|
|
p.print("]:")
|
|
p.printSpace()
|
|
p.printBinding(property.Value)
|
|
|
|
if property.DefaultValueOrNil.Data != nil {
|
|
p.printSpace()
|
|
p.print("=")
|
|
p.printSpace()
|
|
p.printExprWithoutLeadingNewline(property.DefaultValueOrNil, js_ast.LComma, 0)
|
|
}
|
|
continue
|
|
}
|
|
|
|
if str, ok := property.Key.Data.(*js_ast.EString); ok && !property.PreferQuotedKey && p.canPrintIdentifierUTF16(str.Value) {
|
|
// Use a shorthand property if the names are the same
|
|
if id, ok := property.Value.Data.(*js_ast.BIdentifier); ok &&
|
|
!p.willPrintExprCommentsAtLoc(property.Value.Loc) &&
|
|
helpers.UTF16EqualsString(str.Value, p.renamer.NameForSymbol(id.Ref)) {
|
|
if p.options.AddSourceMappings {
|
|
p.addSourceMappingForName(property.Key.Loc, helpers.UTF16ToString(str.Value), id.Ref)
|
|
}
|
|
p.printIdentifierUTF16(str.Value)
|
|
if property.DefaultValueOrNil.Data != nil {
|
|
p.printSpace()
|
|
p.print("=")
|
|
p.printSpace()
|
|
p.printExprWithoutLeadingNewline(property.DefaultValueOrNil, js_ast.LComma, 0)
|
|
}
|
|
continue
|
|
}
|
|
|
|
p.addSourceMapping(property.Key.Loc)
|
|
p.printIdentifierUTF16(str.Value)
|
|
} else if mangled, ok := property.Key.Data.(*js_ast.ENameOfSymbol); ok {
|
|
name := p.mangledPropName(mangled.Ref)
|
|
if p.canPrintIdentifier(name) {
|
|
p.addSourceMappingForName(property.Key.Loc, name, mangled.Ref)
|
|
p.printIdentifier(name)
|
|
|
|
// Use a shorthand property if the names are the same
|
|
if id, ok := property.Value.Data.(*js_ast.BIdentifier); ok &&
|
|
!p.willPrintExprCommentsAtLoc(property.Value.Loc) &&
|
|
name == p.renamer.NameForSymbol(id.Ref) {
|
|
if property.DefaultValueOrNil.Data != nil {
|
|
p.printSpace()
|
|
p.print("=")
|
|
p.printSpace()
|
|
p.printExprWithoutLeadingNewline(property.DefaultValueOrNil, js_ast.LComma, 0)
|
|
}
|
|
continue
|
|
}
|
|
} else {
|
|
p.addSourceMapping(property.Key.Loc)
|
|
p.printQuotedUTF8(name, 0)
|
|
}
|
|
} else {
|
|
p.printExpr(property.Key, js_ast.LLowest, 0)
|
|
}
|
|
|
|
p.print(":")
|
|
p.printSpace()
|
|
}
|
|
p.printBinding(property.Value)
|
|
|
|
if property.DefaultValueOrNil.Data != nil {
|
|
p.printSpace()
|
|
p.print("=")
|
|
p.printSpace()
|
|
p.printExprWithoutLeadingNewline(property.DefaultValueOrNil, js_ast.LComma, 0)
|
|
}
|
|
}
|
|
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.printExprCommentsAfterCloseTokenAtLoc(b.CloseBraceLoc)
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
} else {
|
|
// This block is only reached if len(b.Properties) > 0
|
|
p.printSpace()
|
|
}
|
|
}
|
|
p.addSourceMapping(b.CloseBraceLoc)
|
|
p.print("}")
|
|
|
|
default:
|
|
panic(fmt.Sprintf("Unexpected binding of type %T", binding.Data))
|
|
}
|
|
}
|
|
|
|
func (p *printer) printSpace() {
|
|
if !p.options.MinifyWhitespace {
|
|
p.print(" ")
|
|
}
|
|
}
|
|
|
|
func (p *printer) printNewline() {
|
|
if !p.options.MinifyWhitespace {
|
|
p.print("\n")
|
|
}
|
|
}
|
|
|
|
func (p *printer) currentLineLength() int {
|
|
js := p.js
|
|
n := len(js)
|
|
stop := p.oldLineEnd
|
|
|
|
// Update "oldLineStart" to the start of the current line
|
|
for i := n; i > stop; i-- {
|
|
if c := js[i-1]; c == '\r' || c == '\n' {
|
|
p.oldLineStart = i
|
|
break
|
|
}
|
|
}
|
|
|
|
p.oldLineEnd = n
|
|
return n - p.oldLineStart
|
|
}
|
|
|
|
func (p *printer) printNewlinePastLineLimit() bool {
|
|
if p.currentLineLength() < p.options.LineLimit {
|
|
return false
|
|
}
|
|
p.print("\n")
|
|
p.printIndent()
|
|
return true
|
|
}
|
|
|
|
func (p *printer) printSpaceBeforeOperator(next js_ast.OpCode) {
|
|
if p.prevOpEnd == len(p.js) {
|
|
prev := p.prevOp
|
|
|
|
// "+ + y" => "+ +y"
|
|
// "+ ++ y" => "+ ++y"
|
|
// "x + + y" => "x+ +y"
|
|
// "x ++ + y" => "x+++y"
|
|
// "x + ++ y" => "x+ ++y"
|
|
// "-- >" => "-- >"
|
|
// "< ! --" => "<! --"
|
|
if ((prev == js_ast.BinOpAdd || prev == js_ast.UnOpPos) && (next == js_ast.BinOpAdd || next == js_ast.UnOpPos || next == js_ast.UnOpPreInc)) ||
|
|
((prev == js_ast.BinOpSub || prev == js_ast.UnOpNeg) && (next == js_ast.BinOpSub || next == js_ast.UnOpNeg || next == js_ast.UnOpPreDec)) ||
|
|
(prev == js_ast.UnOpPostDec && next == js_ast.BinOpGt) ||
|
|
(prev == js_ast.UnOpNot && next == js_ast.UnOpPreDec && len(p.js) > 1 && p.js[len(p.js)-2] == '<') {
|
|
p.print(" ")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *printer) printSemicolonAfterStatement() {
|
|
if !p.options.MinifyWhitespace {
|
|
p.print(";\n")
|
|
} else {
|
|
p.needsSemicolon = true
|
|
}
|
|
}
|
|
|
|
func (p *printer) printSemicolonIfNeeded() {
|
|
if p.needsSemicolon {
|
|
p.print(";")
|
|
p.needsSemicolon = false
|
|
}
|
|
}
|
|
|
|
func (p *printer) printSpaceBeforeIdentifier() {
|
|
if c, _ := utf8.DecodeLastRune(p.js); js_ast.IsIdentifierContinue(c) || p.prevRegExpEnd == len(p.js) {
|
|
p.print(" ")
|
|
}
|
|
}
|
|
|
|
type fnArgsOpts struct {
|
|
openParenLoc logger.Loc
|
|
addMappingForOpenParenLoc bool
|
|
hasRestArg bool
|
|
isArrow bool
|
|
}
|
|
|
|
func (p *printer) printFnArgs(args []js_ast.Arg, opts fnArgsOpts) {
|
|
wrap := true
|
|
|
|
// Minify "(a) => {}" as "a=>{}"
|
|
if p.options.MinifyWhitespace && !opts.hasRestArg && opts.isArrow && len(args) == 1 {
|
|
if _, ok := args[0].Binding.Data.(*js_ast.BIdentifier); ok && args[0].DefaultOrNil.Data == nil {
|
|
wrap = false
|
|
}
|
|
}
|
|
|
|
if wrap {
|
|
if opts.addMappingForOpenParenLoc {
|
|
p.addSourceMapping(opts.openParenLoc)
|
|
}
|
|
p.print("(")
|
|
}
|
|
|
|
for i, arg := range args {
|
|
if i != 0 {
|
|
p.print(",")
|
|
p.printSpace()
|
|
}
|
|
p.printDecorators(arg.Decorators, printSpaceAfterDecorator)
|
|
if opts.hasRestArg && i+1 == len(args) {
|
|
p.print("...")
|
|
}
|
|
p.printBinding(arg.Binding)
|
|
|
|
if arg.DefaultOrNil.Data != nil {
|
|
p.printSpace()
|
|
p.print("=")
|
|
p.printSpace()
|
|
p.printExprWithoutLeadingNewline(arg.DefaultOrNil, js_ast.LComma, 0)
|
|
}
|
|
}
|
|
|
|
if wrap {
|
|
p.print(")")
|
|
}
|
|
}
|
|
|
|
func (p *printer) printFn(fn js_ast.Fn) {
|
|
p.printFnArgs(fn.Args, fnArgsOpts{hasRestArg: fn.HasRestArg})
|
|
p.printSpace()
|
|
p.printBlock(fn.Body.Loc, fn.Body.Block)
|
|
}
|
|
|
|
type printAfterDecorator uint8
|
|
|
|
const (
|
|
printNewlineAfterDecorator printAfterDecorator = iota
|
|
printSpaceAfterDecorator
|
|
)
|
|
|
|
func (p *printer) printDecorators(decorators []js_ast.Decorator, defaultMode printAfterDecorator) (omitIndentAfter bool) {
|
|
oldMode := defaultMode
|
|
|
|
for _, decorator := range decorators {
|
|
wrap := false
|
|
wasCallTarget := false
|
|
expr := decorator.Value
|
|
mode := defaultMode
|
|
if decorator.OmitNewlineAfter {
|
|
mode = printSpaceAfterDecorator
|
|
}
|
|
|
|
outer:
|
|
for {
|
|
isCallTarget := wasCallTarget
|
|
wasCallTarget = false
|
|
|
|
switch e := expr.Data.(type) {
|
|
case *js_ast.EIdentifier:
|
|
// "@foo"
|
|
break outer
|
|
|
|
case *js_ast.ECall:
|
|
// "@foo()"
|
|
expr = e.Target
|
|
wasCallTarget = true
|
|
continue
|
|
|
|
case *js_ast.EDot:
|
|
// "@foo.bar"
|
|
if p.canPrintIdentifier(e.Name) {
|
|
expr = e.Target
|
|
continue
|
|
}
|
|
|
|
// "@foo.\u30FF" => "@(foo['\u30FF'])"
|
|
break
|
|
|
|
case *js_ast.EIndex:
|
|
if _, ok := e.Index.Data.(*js_ast.EPrivateIdentifier); ok {
|
|
// "@foo.#bar"
|
|
expr = e.Target
|
|
continue
|
|
}
|
|
|
|
// "@(foo[bar])"
|
|
break
|
|
|
|
case *js_ast.EImportIdentifier:
|
|
ref := ast.FollowSymbols(p.symbols, e.Ref)
|
|
symbol := p.symbols.Get(ref)
|
|
|
|
if symbol.ImportItemStatus == ast.ImportItemMissing {
|
|
// "@(void 0)"
|
|
break
|
|
}
|
|
|
|
if symbol.NamespaceAlias != nil && isCallTarget && e.WasOriginallyIdentifier {
|
|
// "@((0, import_ns.fn)())"
|
|
break
|
|
}
|
|
|
|
if value := p.options.ConstValues[ref]; value.Kind != js_ast.ConstValueNone {
|
|
// "@(<inlined constant>)"
|
|
break
|
|
}
|
|
|
|
// "@foo"
|
|
// "@import_ns.fn"
|
|
break outer
|
|
|
|
default:
|
|
// "@(foo + bar)"
|
|
// "@(() => {})"
|
|
break
|
|
}
|
|
|
|
wrap = true
|
|
break outer
|
|
}
|
|
|
|
p.addSourceMapping(decorator.AtLoc)
|
|
if oldMode == printNewlineAfterDecorator {
|
|
p.printIndent()
|
|
}
|
|
|
|
p.print("@")
|
|
if wrap {
|
|
p.print("(")
|
|
}
|
|
p.printExpr(decorator.Value, js_ast.LLowest, 0)
|
|
if wrap {
|
|
p.print(")")
|
|
}
|
|
|
|
switch mode {
|
|
case printNewlineAfterDecorator:
|
|
p.printNewline()
|
|
|
|
case printSpaceAfterDecorator:
|
|
p.printSpace()
|
|
}
|
|
oldMode = mode
|
|
}
|
|
|
|
omitIndentAfter = oldMode == printSpaceAfterDecorator
|
|
return
|
|
}
|
|
|
|
func (p *printer) printClass(class js_ast.Class) {
|
|
if class.ExtendsOrNil.Data != nil {
|
|
p.print(" extends")
|
|
p.printSpace()
|
|
p.printExpr(class.ExtendsOrNil, js_ast.LNew-1, 0)
|
|
}
|
|
p.printSpace()
|
|
|
|
p.addSourceMapping(class.BodyLoc)
|
|
p.print("{")
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
|
|
for _, item := range class.Properties {
|
|
p.printSemicolonIfNeeded()
|
|
omitIndent := p.printDecorators(item.Decorators, printNewlineAfterDecorator)
|
|
if !omitIndent {
|
|
p.printIndent()
|
|
}
|
|
|
|
if item.Kind == js_ast.PropertyClassStaticBlock {
|
|
p.addSourceMapping(item.Loc)
|
|
p.print("static")
|
|
p.printSpace()
|
|
p.printBlock(item.ClassStaticBlock.Loc, item.ClassStaticBlock.Block)
|
|
p.printNewline()
|
|
continue
|
|
}
|
|
|
|
p.printProperty(item)
|
|
|
|
// Need semicolons after class fields
|
|
if item.ValueOrNil.Data == nil {
|
|
p.printSemicolonAfterStatement()
|
|
} else {
|
|
p.printNewline()
|
|
}
|
|
}
|
|
|
|
p.needsSemicolon = false
|
|
p.printExprCommentsAfterCloseTokenAtLoc(class.CloseBraceLoc)
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
if class.CloseBraceLoc.Start > class.BodyLoc.Start {
|
|
p.addSourceMapping(class.CloseBraceLoc)
|
|
}
|
|
p.print("}")
|
|
}
|
|
|
|
func (p *printer) printProperty(property js_ast.Property) {
|
|
p.printExprCommentsAtLoc(property.Loc)
|
|
|
|
if property.Kind == js_ast.PropertySpread {
|
|
p.addSourceMapping(property.Loc)
|
|
p.print("...")
|
|
p.printExpr(property.ValueOrNil, js_ast.LComma, 0)
|
|
return
|
|
}
|
|
|
|
// Handle key syntax compression for cross-module constant inlining of enums
|
|
var keyFlags printExprFlags
|
|
if p.options.MinifySyntax && property.Flags.Has(js_ast.PropertyIsComputed) {
|
|
property.Key = p.lateConstantFoldUnaryOrBinaryOrIfExpr(property.Key)
|
|
keyFlags |= parentWasUnaryOrBinaryOrIfTest
|
|
|
|
if key, ok := property.Key.Data.(*js_ast.EInlinedEnum); ok {
|
|
property.Key = key.Value
|
|
}
|
|
|
|
// Remove the computed flag if it's no longer needed
|
|
switch key := property.Key.Data.(type) {
|
|
case *js_ast.ENumber:
|
|
property.Flags &= ^js_ast.PropertyIsComputed
|
|
|
|
case *js_ast.EString:
|
|
if !helpers.UTF16EqualsString(key.Value, "__proto__") &&
|
|
!helpers.UTF16EqualsString(key.Value, "constructor") &&
|
|
!helpers.UTF16EqualsString(key.Value, "prototype") {
|
|
property.Flags &= ^js_ast.PropertyIsComputed
|
|
}
|
|
}
|
|
}
|
|
|
|
if property.Flags.Has(js_ast.PropertyIsStatic) {
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(property.Loc)
|
|
p.print("static")
|
|
p.printSpace()
|
|
}
|
|
|
|
switch property.Kind {
|
|
case js_ast.PropertyGetter:
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(property.Loc)
|
|
p.print("get")
|
|
p.printSpace()
|
|
|
|
case js_ast.PropertySetter:
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(property.Loc)
|
|
p.print("set")
|
|
p.printSpace()
|
|
|
|
case js_ast.PropertyAutoAccessor:
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(property.Loc)
|
|
p.print("accessor")
|
|
p.printSpace()
|
|
}
|
|
|
|
if fn, ok := property.ValueOrNil.Data.(*js_ast.EFunction); property.Kind.IsMethodDefinition() && ok {
|
|
if fn.Fn.IsAsync {
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(property.Loc)
|
|
p.print("async")
|
|
p.printSpace()
|
|
}
|
|
if fn.Fn.IsGenerator {
|
|
p.addSourceMapping(property.Loc)
|
|
p.print("*")
|
|
}
|
|
}
|
|
|
|
isComputed := property.Flags.Has(js_ast.PropertyIsComputed)
|
|
|
|
// Automatically print numbers that would cause a syntax error as computed properties
|
|
if !isComputed {
|
|
if key, ok := property.Key.Data.(*js_ast.ENumber); ok {
|
|
if math.Signbit(key.Value) || (key.Value == positiveInfinity && p.options.MinifySyntax) {
|
|
// "{ -1: 0 }" must be printed as "{ [-1]: 0 }"
|
|
// "{ 1/0: 0 }" must be printed as "{ [1/0]: 0 }"
|
|
isComputed = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if isComputed {
|
|
p.addSourceMapping(property.Loc)
|
|
isMultiLine := p.willPrintExprCommentsAtLoc(property.Key.Loc) || p.willPrintExprCommentsAtLoc(property.CloseBracketLoc)
|
|
p.print("[")
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
}
|
|
p.printExpr(property.Key, js_ast.LComma, keyFlags)
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.printExprCommentsAfterCloseTokenAtLoc(property.CloseBracketLoc)
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
}
|
|
if property.CloseBracketLoc.Start > property.Loc.Start {
|
|
p.addSourceMapping(property.CloseBracketLoc)
|
|
}
|
|
p.print("]")
|
|
|
|
if property.ValueOrNil.Data != nil {
|
|
if fn, ok := property.ValueOrNil.Data.(*js_ast.EFunction); property.Kind.IsMethodDefinition() && ok {
|
|
p.printFn(fn.Fn)
|
|
return
|
|
}
|
|
|
|
p.print(":")
|
|
p.printSpace()
|
|
p.printExprWithoutLeadingNewline(property.ValueOrNil, js_ast.LComma, 0)
|
|
}
|
|
|
|
if property.InitializerOrNil.Data != nil {
|
|
p.printSpace()
|
|
p.print("=")
|
|
p.printSpace()
|
|
p.printExprWithoutLeadingNewline(property.InitializerOrNil, js_ast.LComma, 0)
|
|
}
|
|
return
|
|
}
|
|
|
|
switch key := property.Key.Data.(type) {
|
|
case *js_ast.EPrivateIdentifier:
|
|
name := p.renamer.NameForSymbol(key.Ref)
|
|
p.addSourceMappingForName(property.Key.Loc, name, key.Ref)
|
|
p.printIdentifier(name)
|
|
|
|
case *js_ast.ENameOfSymbol:
|
|
name := p.mangledPropName(key.Ref)
|
|
if p.canPrintIdentifier(name) {
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMappingForName(property.Key.Loc, name, key.Ref)
|
|
p.printIdentifier(name)
|
|
|
|
// Use a shorthand property if the names are the same
|
|
if !p.options.UnsupportedFeatures.Has(compat.ObjectExtensions) && property.ValueOrNil.Data != nil && !p.willPrintExprCommentsAtLoc(property.ValueOrNil.Loc) {
|
|
switch e := property.ValueOrNil.Data.(type) {
|
|
case *js_ast.EIdentifier:
|
|
if name == p.renamer.NameForSymbol(e.Ref) {
|
|
if property.InitializerOrNil.Data != nil {
|
|
p.printSpace()
|
|
p.print("=")
|
|
p.printSpace()
|
|
p.printExprWithoutLeadingNewline(property.InitializerOrNil, js_ast.LComma, 0)
|
|
}
|
|
return
|
|
}
|
|
|
|
case *js_ast.EImportIdentifier:
|
|
// Make sure we're not using a property access instead of an identifier
|
|
ref := ast.FollowSymbols(p.symbols, e.Ref)
|
|
if symbol := p.symbols.Get(ref); symbol.NamespaceAlias == nil && name == p.renamer.NameForSymbol(ref) &&
|
|
p.options.ConstValues[ref].Kind == js_ast.ConstValueNone {
|
|
if property.InitializerOrNil.Data != nil {
|
|
p.printSpace()
|
|
p.print("=")
|
|
p.printSpace()
|
|
p.printExprWithoutLeadingNewline(property.InitializerOrNil, js_ast.LComma, 0)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
p.addSourceMapping(property.Key.Loc)
|
|
p.printQuotedUTF8(name, 0)
|
|
}
|
|
|
|
case *js_ast.EString:
|
|
if !property.Flags.Has(js_ast.PropertyPreferQuotedKey) && p.canPrintIdentifierUTF16(key.Value) {
|
|
p.printSpaceBeforeIdentifier()
|
|
|
|
// Use a shorthand property if the names are the same
|
|
if !p.options.UnsupportedFeatures.Has(compat.ObjectExtensions) && property.ValueOrNil.Data != nil && !p.willPrintExprCommentsAtLoc(property.ValueOrNil.Loc) {
|
|
switch e := property.ValueOrNil.Data.(type) {
|
|
case *js_ast.EIdentifier:
|
|
if canUseShorthandProperty(key.Value, p.renamer.NameForSymbol(e.Ref), property.Flags) {
|
|
if p.options.AddSourceMappings {
|
|
p.addSourceMappingForName(property.Key.Loc, helpers.UTF16ToString(key.Value), e.Ref)
|
|
}
|
|
p.printIdentifierUTF16(key.Value)
|
|
if property.InitializerOrNil.Data != nil {
|
|
p.printSpace()
|
|
p.print("=")
|
|
p.printSpace()
|
|
p.printExprWithoutLeadingNewline(property.InitializerOrNil, js_ast.LComma, 0)
|
|
}
|
|
return
|
|
}
|
|
|
|
case *js_ast.EImportIdentifier:
|
|
// Make sure we're not using a property access instead of an identifier
|
|
ref := ast.FollowSymbols(p.symbols, e.Ref)
|
|
if symbol := p.symbols.Get(ref); symbol.NamespaceAlias == nil && canUseShorthandProperty(key.Value, p.renamer.NameForSymbol(ref), property.Flags) &&
|
|
p.options.ConstValues[ref].Kind == js_ast.ConstValueNone {
|
|
if p.options.AddSourceMappings {
|
|
p.addSourceMappingForName(property.Key.Loc, helpers.UTF16ToString(key.Value), ref)
|
|
}
|
|
p.printIdentifierUTF16(key.Value)
|
|
if property.InitializerOrNil.Data != nil {
|
|
p.printSpace()
|
|
p.print("=")
|
|
p.printSpace()
|
|
p.printExprWithoutLeadingNewline(property.InitializerOrNil, js_ast.LComma, 0)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// The JavaScript specification special-cases the property identifier
|
|
// "__proto__" with a colon after it to set the prototype of the object.
|
|
// If we keep the identifier but add a colon then we'll cause a behavior
|
|
// change because the prototype will now be set. Avoid using an identifier
|
|
// by using a computed property with a string instead. For more info see:
|
|
// https://tc39.es/ecma262/#sec-runtime-semantics-propertydefinitionevaluation
|
|
if property.Flags.Has(js_ast.PropertyWasShorthand) && !p.options.UnsupportedFeatures.Has(compat.ObjectExtensions) &&
|
|
helpers.UTF16EqualsString(key.Value, "__proto__") {
|
|
p.print("[")
|
|
p.addSourceMapping(property.Key.Loc)
|
|
p.printQuotedUTF16(key.Value, 0)
|
|
p.print("]")
|
|
break
|
|
}
|
|
|
|
p.addSourceMapping(property.Key.Loc)
|
|
p.printIdentifierUTF16(key.Value)
|
|
} else {
|
|
p.addSourceMapping(property.Key.Loc)
|
|
p.printQuotedUTF16(key.Value, 0)
|
|
}
|
|
|
|
default:
|
|
p.printExpr(property.Key, js_ast.LLowest, keyFlags)
|
|
}
|
|
|
|
if fn, ok := property.ValueOrNil.Data.(*js_ast.EFunction); property.Kind.IsMethodDefinition() && ok {
|
|
p.printFn(fn.Fn)
|
|
return
|
|
}
|
|
|
|
if property.ValueOrNil.Data != nil {
|
|
p.print(":")
|
|
p.printSpace()
|
|
p.printExprWithoutLeadingNewline(property.ValueOrNil, js_ast.LComma, 0)
|
|
}
|
|
|
|
if property.InitializerOrNil.Data != nil {
|
|
p.printSpace()
|
|
p.print("=")
|
|
p.printSpace()
|
|
p.printExprWithoutLeadingNewline(property.InitializerOrNil, js_ast.LComma, 0)
|
|
}
|
|
}
|
|
|
|
func canUseShorthandProperty(key []uint16, name string, flags js_ast.PropertyFlags) bool {
|
|
// The JavaScript specification special-cases the property identifier
|
|
// "__proto__" with a colon after it to set the prototype of the object. If
|
|
// we remove the colon then we'll cause a behavior change because the
|
|
// prototype will no longer be set, but we also don't want to add a colon
|
|
// if it was omitted. Always use a shorthand property if the property is not
|
|
// "__proto__", otherwise try to preserve the original shorthand status. See:
|
|
// https://tc39.es/ecma262/#sec-runtime-semantics-propertydefinitionevaluation
|
|
if !helpers.UTF16EqualsString(key, name) {
|
|
return false
|
|
}
|
|
return helpers.UTF16EqualsString(key, name) && (name != "__proto__" || flags.Has(js_ast.PropertyWasShorthand))
|
|
}
|
|
|
|
func (p *printer) printQuotedUTF16(data []uint16, flags printQuotedFlags) {
|
|
if p.options.UnsupportedFeatures.Has(compat.TemplateLiteral) {
|
|
flags &= ^printQuotedAllowBacktick
|
|
}
|
|
|
|
singleCost := 0
|
|
doubleCost := 0
|
|
backtickCost := 0
|
|
|
|
for i, c := range data {
|
|
switch c {
|
|
case '\n':
|
|
if p.options.MinifySyntax {
|
|
// The backslash for the newline costs an extra character for old-style
|
|
// string literals when compared to a template literal
|
|
backtickCost--
|
|
}
|
|
case '\'':
|
|
singleCost++
|
|
case '"':
|
|
doubleCost++
|
|
case '`':
|
|
backtickCost++
|
|
case '$':
|
|
// "${" sequences need to be escaped in template literals
|
|
if i+1 < len(data) && data[i+1] == '{' {
|
|
backtickCost++
|
|
}
|
|
}
|
|
}
|
|
|
|
c := "\""
|
|
if doubleCost > singleCost {
|
|
c = "'"
|
|
if singleCost > backtickCost && (flags&printQuotedAllowBacktick) != 0 {
|
|
c = "`"
|
|
}
|
|
} else if doubleCost > backtickCost && (flags&printQuotedAllowBacktick) != 0 {
|
|
c = "`"
|
|
}
|
|
|
|
p.print(c)
|
|
p.printUnquotedUTF16(data, rune(c[0]), flags)
|
|
p.print(c)
|
|
}
|
|
|
|
func (p *printer) printRequireOrImportExpr(importRecordIndex uint32, level js_ast.L, flags printExprFlags, closeParenLoc logger.Loc, phase ast.ImportPhase) {
|
|
record := &p.importRecords[importRecordIndex]
|
|
|
|
if level >= js_ast.LNew || (flags&isNewTarget) != 0 {
|
|
p.print("(")
|
|
defer p.print(")")
|
|
level = js_ast.LLowest
|
|
}
|
|
|
|
if !record.SourceIndex.IsValid() {
|
|
// External "require()"
|
|
if record.Kind != ast.ImportDynamic {
|
|
// Wrap this with a call to "__toESM()" if this is a CommonJS file
|
|
wrapWithToESM := record.Flags.Has(ast.WrapWithToESM)
|
|
if wrapWithToESM {
|
|
p.printSpaceBeforeIdentifier()
|
|
p.printIdentifier(p.renamer.NameForSymbol(p.options.ToESMRef))
|
|
p.print("(")
|
|
}
|
|
|
|
// Potentially substitute our own "__require" stub for "require"
|
|
p.printSpaceBeforeIdentifier()
|
|
if record.Flags.Has(ast.CallRuntimeRequire) {
|
|
p.printIdentifier(p.renamer.NameForSymbol(p.options.RuntimeRequireRef))
|
|
} else {
|
|
p.print("require")
|
|
}
|
|
|
|
isMultiLine := p.willPrintExprCommentsAtLoc(record.Range.Loc) || p.willPrintExprCommentsAtLoc(closeParenLoc)
|
|
p.print("(")
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
}
|
|
p.printExprCommentsAtLoc(record.Range.Loc)
|
|
p.printPath(importRecordIndex, ast.ImportRequire)
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.printExprCommentsAfterCloseTokenAtLoc(closeParenLoc)
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
}
|
|
if closeParenLoc.Start > record.Range.Loc.Start {
|
|
p.addSourceMapping(closeParenLoc)
|
|
}
|
|
p.print(")")
|
|
|
|
// Finish the call to "__toESM()"
|
|
if wrapWithToESM {
|
|
if p.moduleType.IsESM() {
|
|
p.print(",")
|
|
p.printSpace()
|
|
p.print("1")
|
|
}
|
|
p.print(")")
|
|
}
|
|
return
|
|
}
|
|
|
|
// External "import()"
|
|
kind := ast.ImportDynamic
|
|
if !p.options.UnsupportedFeatures.Has(compat.DynamicImport) {
|
|
p.printSpaceBeforeIdentifier()
|
|
switch phase {
|
|
case ast.DeferPhase:
|
|
p.print("import.defer(")
|
|
case ast.SourcePhase:
|
|
p.print("import.source(")
|
|
default:
|
|
p.print("import(")
|
|
}
|
|
} else {
|
|
kind = ast.ImportRequire
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("Promise.resolve()")
|
|
p.printDotThenPrefix()
|
|
defer p.printDotThenSuffix()
|
|
|
|
// Wrap this with a call to "__toESM()" if this is a CommonJS file
|
|
if record.Flags.Has(ast.WrapWithToESM) {
|
|
p.printSpaceBeforeIdentifier()
|
|
p.printIdentifier(p.renamer.NameForSymbol(p.options.ToESMRef))
|
|
p.print("(")
|
|
defer func() {
|
|
if p.moduleType.IsESM() {
|
|
p.print(",")
|
|
p.printSpace()
|
|
p.print("1")
|
|
}
|
|
p.print(")")
|
|
}()
|
|
}
|
|
|
|
// Potentially substitute our own "__require" stub for "require"
|
|
p.printSpaceBeforeIdentifier()
|
|
if record.Flags.Has(ast.CallRuntimeRequire) {
|
|
p.printIdentifier(p.renamer.NameForSymbol(p.options.RuntimeRequireRef))
|
|
} else {
|
|
p.print("require")
|
|
}
|
|
|
|
p.print("(")
|
|
}
|
|
isMultiLine := p.willPrintExprCommentsAtLoc(record.Range.Loc) ||
|
|
p.willPrintExprCommentsAtLoc(closeParenLoc) ||
|
|
(record.AssertOrWith != nil &&
|
|
!p.options.UnsupportedFeatures.Has(compat.DynamicImport) &&
|
|
(!p.options.UnsupportedFeatures.Has(compat.ImportAssertions) ||
|
|
!p.options.UnsupportedFeatures.Has(compat.ImportAttributes)) &&
|
|
p.willPrintExprCommentsAtLoc(record.AssertOrWith.OuterOpenBraceLoc))
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
}
|
|
p.printExprCommentsAtLoc(record.Range.Loc)
|
|
p.printPath(importRecordIndex, kind)
|
|
if !p.options.UnsupportedFeatures.Has(compat.DynamicImport) {
|
|
p.printImportCallAssertOrWith(record.AssertOrWith, isMultiLine)
|
|
}
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.printExprCommentsAfterCloseTokenAtLoc(closeParenLoc)
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
}
|
|
if closeParenLoc.Start > record.Range.Loc.Start {
|
|
p.addSourceMapping(closeParenLoc)
|
|
}
|
|
p.print(")")
|
|
return
|
|
}
|
|
|
|
meta := p.options.RequireOrImportMetaForSource(record.SourceIndex.GetIndex())
|
|
|
|
// Don't need the namespace object if the result is unused anyway
|
|
if (flags & exprResultIsUnused) != 0 {
|
|
meta.ExportsRef = ast.InvalidRef
|
|
}
|
|
|
|
// Internal "import()" of async ESM
|
|
if record.Kind == ast.ImportDynamic && meta.IsWrapperAsync {
|
|
p.printSpaceBeforeIdentifier()
|
|
p.printIdentifier(p.renamer.NameForSymbol(meta.WrapperRef))
|
|
p.print("()")
|
|
if meta.ExportsRef != ast.InvalidRef {
|
|
p.printDotThenPrefix()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.printIdentifier(p.renamer.NameForSymbol(meta.ExportsRef))
|
|
p.printDotThenSuffix()
|
|
}
|
|
return
|
|
}
|
|
|
|
// Internal "require()" or "import()"
|
|
if record.Kind == ast.ImportDynamic {
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("Promise.resolve()")
|
|
level = p.printDotThenPrefix()
|
|
defer p.printDotThenSuffix()
|
|
}
|
|
|
|
// Make sure the comma operator is properly wrapped
|
|
if meta.ExportsRef != ast.InvalidRef && level >= js_ast.LComma {
|
|
p.print("(")
|
|
defer p.print(")")
|
|
}
|
|
|
|
// Wrap this with a call to "__toESM()" if this is a CommonJS file
|
|
wrapWithToESM := record.Flags.Has(ast.WrapWithToESM)
|
|
if wrapWithToESM {
|
|
p.printSpaceBeforeIdentifier()
|
|
p.printIdentifier(p.renamer.NameForSymbol(p.options.ToESMRef))
|
|
p.print("(")
|
|
}
|
|
|
|
// Call the wrapper
|
|
p.printSpaceBeforeIdentifier()
|
|
p.printIdentifier(p.renamer.NameForSymbol(meta.WrapperRef))
|
|
p.print("()")
|
|
|
|
// Return the namespace object if this is an ESM file
|
|
if meta.ExportsRef != ast.InvalidRef {
|
|
p.print(",")
|
|
p.printSpace()
|
|
|
|
// Wrap this with a call to "__toCommonJS()" if this is an ESM file
|
|
wrapWithTpCJS := record.Flags.Has(ast.WrapWithToCJS)
|
|
if wrapWithTpCJS {
|
|
p.printIdentifier(p.renamer.NameForSymbol(p.options.ToCommonJSRef))
|
|
p.print("(")
|
|
}
|
|
p.printIdentifier(p.renamer.NameForSymbol(meta.ExportsRef))
|
|
if wrapWithTpCJS {
|
|
p.print(")")
|
|
}
|
|
}
|
|
|
|
// Finish the call to "__toESM()"
|
|
if wrapWithToESM {
|
|
if p.moduleType.IsESM() {
|
|
p.print(",")
|
|
p.printSpace()
|
|
p.print("1")
|
|
}
|
|
p.print(")")
|
|
}
|
|
}
|
|
|
|
func (p *printer) printDotThenPrefix() js_ast.L {
|
|
if p.options.UnsupportedFeatures.Has(compat.Arrow) {
|
|
p.print(".then(function()")
|
|
p.printSpace()
|
|
p.print("{")
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
p.print("return")
|
|
p.printSpace()
|
|
return js_ast.LLowest
|
|
} else {
|
|
p.print(".then(()")
|
|
p.printSpace()
|
|
p.print("=>")
|
|
p.printSpace()
|
|
return js_ast.LComma
|
|
}
|
|
}
|
|
|
|
func (p *printer) printDotThenSuffix() {
|
|
if p.options.UnsupportedFeatures.Has(compat.Arrow) {
|
|
if !p.options.MinifyWhitespace {
|
|
p.print(";")
|
|
}
|
|
p.printNewline()
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
p.print("})")
|
|
} else {
|
|
p.print(")")
|
|
}
|
|
}
|
|
|
|
func (p *printer) printUndefined(loc logger.Loc, level js_ast.L) {
|
|
if level >= js_ast.LPrefix {
|
|
p.addSourceMapping(loc)
|
|
p.print("(void 0)")
|
|
} else {
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(loc)
|
|
p.print("void 0")
|
|
}
|
|
}
|
|
|
|
// Call this before printing an expression to see if it turned out to be empty.
|
|
// We use this to do inlining of empty functions at print time. It can't happen
|
|
// during parse time because a) parse time only has two passes and we only know
|
|
// if a function can be inlined at the end of the second pass (due to is-mutated
|
|
// analysis) and b) we want to enable cross-module inlining of empty functions
|
|
// which has to happen after linking.
|
|
//
|
|
// This function returns "nil" to indicate that the expression should be removed
|
|
// completely.
|
|
//
|
|
// This function doesn't need to search everywhere inside the entire expression
|
|
// for calls to inline. Calls are automatically inlined when printed. However,
|
|
// the printer replaces the call with "undefined" since the result may still
|
|
// be needed by the caller. If the caller knows that it doesn't need the result,
|
|
// it should call this function first instead so we don't print "undefined".
|
|
//
|
|
// This is a separate function instead of trying to work this logic into the
|
|
// printer because it's too late to eliminate the expression entirely when we're
|
|
// in the printer. We may have already printed the leading indent, for example.
|
|
func (p *printer) simplifyUnusedExpr(expr js_ast.Expr) js_ast.Expr {
|
|
switch e := expr.Data.(type) {
|
|
case *js_ast.EBinary:
|
|
// Calls to be inlined may be hidden inside a comma operator chain
|
|
if e.Op == js_ast.BinOpComma {
|
|
left := p.simplifyUnusedExpr(e.Left)
|
|
right := p.simplifyUnusedExpr(e.Right)
|
|
if left.Data != e.Left.Data || right.Data != e.Right.Data {
|
|
return js_ast.JoinWithComma(left, right)
|
|
}
|
|
}
|
|
|
|
case *js_ast.ECall:
|
|
var symbolFlags ast.SymbolFlags
|
|
switch target := e.Target.Data.(type) {
|
|
case *js_ast.EIdentifier:
|
|
symbolFlags = p.symbols.Get(target.Ref).Flags
|
|
case *js_ast.EImportIdentifier:
|
|
ref := ast.FollowSymbols(p.symbols, target.Ref)
|
|
symbolFlags = p.symbols.Get(ref).Flags
|
|
}
|
|
|
|
// Replace non-mutated empty functions with their arguments at print time
|
|
if (symbolFlags & (ast.IsEmptyFunction | ast.CouldPotentiallyBeMutated)) == ast.IsEmptyFunction {
|
|
var replacement js_ast.Expr
|
|
for _, arg := range e.Args {
|
|
if _, ok := arg.Data.(*js_ast.ESpread); ok {
|
|
arg.Data = &js_ast.EArray{Items: []js_ast.Expr{arg}, IsSingleLine: true}
|
|
}
|
|
replacement = js_ast.JoinWithComma(replacement, p.astHelpers.SimplifyUnusedExpr(p.simplifyUnusedExpr(arg), p.options.UnsupportedFeatures))
|
|
}
|
|
return replacement // Don't add "undefined" here because the result isn't used
|
|
}
|
|
|
|
// Inline non-mutated identity functions at print time
|
|
if (symbolFlags&(ast.IsIdentityFunction|ast.CouldPotentiallyBeMutated)) == ast.IsIdentityFunction && len(e.Args) == 1 {
|
|
arg := e.Args[0]
|
|
if _, ok := arg.Data.(*js_ast.ESpread); !ok {
|
|
return p.astHelpers.SimplifyUnusedExpr(p.simplifyUnusedExpr(arg), p.options.UnsupportedFeatures)
|
|
}
|
|
}
|
|
}
|
|
|
|
return expr
|
|
}
|
|
|
|
// This assumes the original expression was some form of indirect value, such
|
|
// as a value returned from a function call or the result of a comma operator.
|
|
// In this case, there is no special behavior with the "delete" operator or
|
|
// with function calls. If we substitute this indirect value for another value
|
|
// due to inlining, we have to make sure we don't accidentally introduce special
|
|
// behavior.
|
|
func (p *printer) guardAgainstBehaviorChangeDueToSubstitution(expr js_ast.Expr, flags printExprFlags) js_ast.Expr {
|
|
wrap := false
|
|
|
|
if (flags & isDeleteTarget) != 0 {
|
|
// "delete id(x)" must not become "delete x"
|
|
// "delete (empty(), x)" must not become "delete x"
|
|
if binary, ok := expr.Data.(*js_ast.EBinary); !ok || binary.Op != js_ast.BinOpComma {
|
|
wrap = true
|
|
}
|
|
} else if (flags & isCallTargetOrTemplateTag) != 0 {
|
|
// "id(x.y)()" must not become "x.y()"
|
|
// "id(x.y)``" must not become "x.y``"
|
|
// "(empty(), x.y)()" must not become "x.y()"
|
|
// "(empty(), eval)()" must not become "eval()"
|
|
switch expr.Data.(type) {
|
|
case *js_ast.EDot, *js_ast.EIndex:
|
|
wrap = true
|
|
case *js_ast.EIdentifier:
|
|
if p.isUnboundEvalIdentifier(expr) {
|
|
wrap = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if wrap {
|
|
expr.Data = &js_ast.EBinary{
|
|
Op: js_ast.BinOpComma,
|
|
Left: js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ENumber{Value: 0}},
|
|
Right: expr,
|
|
}
|
|
}
|
|
|
|
return expr
|
|
}
|
|
|
|
// Constant folding is already implemented once in the parser. A smaller form
|
|
// of constant folding (just for numbers) is implemented here to clean up cross-
|
|
// module numeric constants and bitwise operations. This is not a general-
|
|
// purpose/optimal approach and never will be. For example, we can't affect
|
|
// tree shaking at this stage because it has already happened.
|
|
func (p *printer) lateConstantFoldUnaryOrBinaryOrIfExpr(expr js_ast.Expr) js_ast.Expr {
|
|
switch e := expr.Data.(type) {
|
|
case *js_ast.EImportIdentifier:
|
|
ref := ast.FollowSymbols(p.symbols, e.Ref)
|
|
if value := p.options.ConstValues[ref]; value.Kind != js_ast.ConstValueNone {
|
|
return js_ast.ConstValueToExpr(expr.Loc, value)
|
|
}
|
|
|
|
case *js_ast.EDot:
|
|
if value, ok := p.tryToGetImportedEnumValue(e.Target, e.Name); ok {
|
|
var inlinedValue js_ast.Expr
|
|
if value.String != nil {
|
|
inlinedValue = js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EString{Value: value.String}}
|
|
} else {
|
|
inlinedValue = js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ENumber{Value: value.Number}}
|
|
}
|
|
|
|
if strings.Contains(e.Name, "*/") {
|
|
// Don't wrap with a comment
|
|
return inlinedValue
|
|
}
|
|
|
|
// Wrap with a comment
|
|
return js_ast.Expr{Loc: inlinedValue.Loc, Data: &js_ast.EInlinedEnum{
|
|
Value: inlinedValue,
|
|
Comment: e.Name,
|
|
}}
|
|
}
|
|
|
|
case *js_ast.EUnary:
|
|
value := p.lateConstantFoldUnaryOrBinaryOrIfExpr(e.Value)
|
|
|
|
// Only fold again if something chained
|
|
if value.Data != e.Value.Data {
|
|
// Only fold certain operations (just like the parser)
|
|
if v, ok := js_ast.ToNumberWithoutSideEffects(value.Data); ok {
|
|
switch e.Op {
|
|
case js_ast.UnOpPos:
|
|
return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ENumber{Value: v}}
|
|
|
|
case js_ast.UnOpNeg:
|
|
return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ENumber{Value: -v}}
|
|
|
|
case js_ast.UnOpCpl:
|
|
return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ENumber{Value: float64(^js_ast.ToInt32(v))}}
|
|
}
|
|
}
|
|
|
|
// Don't mutate the original AST
|
|
expr.Data = &js_ast.EUnary{Op: e.Op, Value: value}
|
|
}
|
|
|
|
case *js_ast.EBinary:
|
|
left := p.lateConstantFoldUnaryOrBinaryOrIfExpr(e.Left)
|
|
right := p.lateConstantFoldUnaryOrBinaryOrIfExpr(e.Right)
|
|
|
|
// Only fold again if something changed
|
|
if left.Data != e.Left.Data || right.Data != e.Right.Data {
|
|
binary := &js_ast.EBinary{Op: e.Op, Left: left, Right: right}
|
|
|
|
// Only fold certain operations (just like the parser)
|
|
if js_ast.ShouldFoldBinaryOperatorWhenMinifying(binary) {
|
|
if result := js_ast.FoldBinaryOperator(expr.Loc, binary); result.Data != nil {
|
|
return result
|
|
}
|
|
}
|
|
|
|
// Don't mutate the original AST
|
|
expr.Data = binary
|
|
}
|
|
|
|
case *js_ast.EIf:
|
|
test := p.lateConstantFoldUnaryOrBinaryOrIfExpr(e.Test)
|
|
|
|
// Only fold again if something changed
|
|
if test.Data != e.Test.Data {
|
|
if boolean, sideEffects, ok := js_ast.ToBooleanWithSideEffects(test.Data); ok && sideEffects == js_ast.NoSideEffects {
|
|
if boolean {
|
|
return p.lateConstantFoldUnaryOrBinaryOrIfExpr(e.Yes)
|
|
} else {
|
|
return p.lateConstantFoldUnaryOrBinaryOrIfExpr(e.No)
|
|
}
|
|
}
|
|
|
|
// Don't mutate the original AST
|
|
expr.Data = &js_ast.EIf{Test: test, Yes: e.Yes, No: e.No}
|
|
}
|
|
}
|
|
|
|
return expr
|
|
}
|
|
|
|
func (p *printer) isUnboundIdentifier(expr js_ast.Expr) bool {
|
|
id, ok := expr.Data.(*js_ast.EIdentifier)
|
|
return ok && p.symbols.Get(ast.FollowSymbols(p.symbols, id.Ref)).Kind == ast.SymbolUnbound
|
|
}
|
|
|
|
func (p *printer) isIdentifierOrNumericConstantOrPropertyAccess(expr js_ast.Expr) bool {
|
|
switch e := expr.Data.(type) {
|
|
case *js_ast.EIdentifier, *js_ast.EDot, *js_ast.EIndex:
|
|
return true
|
|
case *js_ast.ENumber:
|
|
return math.IsInf(e.Value, 1) || math.IsNaN(e.Value)
|
|
}
|
|
return false
|
|
}
|
|
|
|
type exprStartFlags uint8
|
|
|
|
const (
|
|
stmtStartFlag exprStartFlags = 1 << iota
|
|
exportDefaultStartFlag
|
|
arrowExprStartFlag
|
|
forOfInitStartFlag
|
|
)
|
|
|
|
func (p *printer) saveExprStartFlags() (flags exprStartFlags) {
|
|
n := len(p.js)
|
|
if p.stmtStart == n {
|
|
flags |= stmtStartFlag
|
|
}
|
|
if p.exportDefaultStart == n {
|
|
flags |= exportDefaultStartFlag
|
|
}
|
|
if p.arrowExprStart == n {
|
|
flags |= arrowExprStartFlag
|
|
}
|
|
if p.forOfInitStart == n {
|
|
flags |= forOfInitStartFlag
|
|
}
|
|
return
|
|
}
|
|
|
|
func (p *printer) restoreExprStartFlags(flags exprStartFlags) {
|
|
if flags != 0 {
|
|
n := len(p.js)
|
|
if (flags & stmtStartFlag) != 0 {
|
|
p.stmtStart = n
|
|
}
|
|
if (flags & exportDefaultStartFlag) != 0 {
|
|
p.exportDefaultStart = n
|
|
}
|
|
if (flags & arrowExprStartFlag) != 0 {
|
|
p.arrowExprStart = n
|
|
}
|
|
if (flags & forOfInitStartFlag) != 0 {
|
|
p.forOfInitStart = n
|
|
}
|
|
}
|
|
}
|
|
|
|
// Print any stored comments that are associated with this location
|
|
func (p *printer) printExprCommentsAtLoc(loc logger.Loc) {
|
|
if p.options.MinifyWhitespace {
|
|
return
|
|
}
|
|
if comments := p.exprComments[loc]; comments != nil && !p.printedExprComments[loc] {
|
|
flags := p.saveExprStartFlags()
|
|
|
|
// We must never generate a newline before certain expressions. For example,
|
|
// generating a newline before the expression in a "return" statement will
|
|
// cause a semicolon to be inserted, which would change the code's behavior.
|
|
if p.noLeadingNewlineHere == len(p.js) {
|
|
for _, comment := range comments {
|
|
if strings.HasPrefix(comment, "//") {
|
|
p.print("/*")
|
|
p.print(comment[2:])
|
|
if strings.HasPrefix(comment, "// ") {
|
|
p.print(" ")
|
|
}
|
|
p.print("*/")
|
|
} else {
|
|
p.print(strings.Join(strings.Split(comment, "\n"), ""))
|
|
}
|
|
p.printSpace()
|
|
}
|
|
} else {
|
|
for _, comment := range comments {
|
|
p.printIndentedComment(comment)
|
|
p.printIndent()
|
|
}
|
|
}
|
|
|
|
// Mark these comments as printed so we don't print them again
|
|
p.printedExprComments[loc] = true
|
|
|
|
p.restoreExprStartFlags(flags)
|
|
}
|
|
}
|
|
|
|
func (p *printer) printExprCommentsAfterCloseTokenAtLoc(loc logger.Loc) {
|
|
if comments := p.exprComments[loc]; comments != nil && !p.printedExprComments[loc] {
|
|
flags := p.saveExprStartFlags()
|
|
|
|
for _, comment := range comments {
|
|
p.printIndent()
|
|
p.printIndentedComment(comment)
|
|
}
|
|
|
|
// Mark these comments as printed so we don't print them again
|
|
p.printedExprComments[loc] = true
|
|
|
|
p.restoreExprStartFlags(flags)
|
|
}
|
|
}
|
|
|
|
func (p *printer) printExprWithoutLeadingNewline(expr js_ast.Expr, level js_ast.L, flags printExprFlags) {
|
|
if !p.options.MinifyWhitespace && p.willPrintExprCommentsAtLoc(expr.Loc) {
|
|
p.print("(")
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
p.printExpr(expr, level, flags)
|
|
p.printNewline()
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
p.print(")")
|
|
return
|
|
}
|
|
|
|
p.noLeadingNewlineHere = len(p.js)
|
|
p.printExpr(expr, level, flags)
|
|
}
|
|
|
|
type printExprFlags uint16
|
|
|
|
const (
|
|
isNewTarget printExprFlags = 1 << iota
|
|
forbidIn
|
|
hasNonOptionalChainParent
|
|
exprResultIsUnused
|
|
didAlreadySimplifyUnusedExprs
|
|
isFollowedByOf
|
|
isInsideForAwait
|
|
isDeleteTarget
|
|
isCallTargetOrTemplateTag
|
|
isPropertyAccessTarget
|
|
parentWasUnaryOrBinaryOrIfTest
|
|
)
|
|
|
|
func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFlags) {
|
|
// If syntax compression is enabled, do a pre-pass over unary and binary
|
|
// operators to inline bitwise operations of cross-module inlined constants.
|
|
// This makes the output a little tighter if people construct bit masks in
|
|
// other files. This is not a general-purpose constant folding pass. In
|
|
// particular, it has no effect on tree shaking because that pass has already
|
|
// been run.
|
|
//
|
|
// This sets a flag to avoid doing this when the parent is a unary or binary
|
|
// operator so that we don't trigger O(n^2) behavior when traversing over a
|
|
// large expression tree.
|
|
if p.options.MinifySyntax && (flags&parentWasUnaryOrBinaryOrIfTest) == 0 {
|
|
switch expr.Data.(type) {
|
|
case *js_ast.EUnary, *js_ast.EBinary, *js_ast.EIf:
|
|
expr = p.lateConstantFoldUnaryOrBinaryOrIfExpr(expr)
|
|
}
|
|
}
|
|
|
|
p.printExprCommentsAtLoc(expr.Loc)
|
|
|
|
switch e := expr.Data.(type) {
|
|
case *js_ast.EMissing:
|
|
p.addSourceMapping(expr.Loc)
|
|
|
|
case *js_ast.EAnnotation:
|
|
p.printExpr(e.Value, level, flags)
|
|
|
|
case *js_ast.EUndefined:
|
|
p.printUndefined(expr.Loc, level)
|
|
|
|
case *js_ast.ESuper:
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(expr.Loc)
|
|
p.print("super")
|
|
|
|
case *js_ast.ENull:
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(expr.Loc)
|
|
p.print("null")
|
|
|
|
case *js_ast.EThis:
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(expr.Loc)
|
|
p.print("this")
|
|
|
|
case *js_ast.ESpread:
|
|
p.addSourceMapping(expr.Loc)
|
|
p.print("...")
|
|
p.printExpr(e.Value, js_ast.LComma, 0)
|
|
|
|
case *js_ast.ENewTarget:
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(expr.Loc)
|
|
p.print("new.target")
|
|
|
|
case *js_ast.EImportMeta:
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(expr.Loc)
|
|
p.print("import.meta")
|
|
|
|
case *js_ast.ENameOfSymbol:
|
|
name := p.mangledPropName(e.Ref)
|
|
p.addSourceMappingForName(expr.Loc, name, e.Ref)
|
|
|
|
if !p.options.MinifyWhitespace && e.HasPropertyKeyComment {
|
|
p.print("/* @__KEY__ */ ")
|
|
}
|
|
|
|
p.printQuotedUTF8(name, printQuotedAllowBacktick)
|
|
|
|
case *js_ast.EJSXElement:
|
|
// Start the opening tag
|
|
p.addSourceMapping(expr.Loc)
|
|
p.print("<")
|
|
p.printJSXTag(e.TagOrNil)
|
|
if !e.IsTagSingleLine {
|
|
p.options.Indent++
|
|
}
|
|
|
|
// Print the attributes
|
|
for _, property := range e.Properties {
|
|
if e.IsTagSingleLine {
|
|
p.printSpace()
|
|
} else {
|
|
p.printNewline()
|
|
p.printIndent()
|
|
}
|
|
|
|
if property.Kind == js_ast.PropertySpread {
|
|
if p.willPrintExprCommentsAtLoc(property.Loc) {
|
|
p.print("{")
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
p.printExprCommentsAtLoc(property.Loc)
|
|
p.print("...")
|
|
p.printExpr(property.ValueOrNil, js_ast.LComma, 0)
|
|
p.printNewline()
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
p.print("}")
|
|
} else {
|
|
p.print("{...")
|
|
p.printExpr(property.ValueOrNil, js_ast.LComma, 0)
|
|
p.print("}")
|
|
}
|
|
continue
|
|
}
|
|
|
|
p.printSpaceBeforeIdentifier()
|
|
if mangled, ok := property.Key.Data.(*js_ast.ENameOfSymbol); ok {
|
|
name := p.mangledPropName(mangled.Ref)
|
|
p.addSourceMappingForName(property.Key.Loc, name, mangled.Ref)
|
|
p.printIdentifier(name)
|
|
} else if str, ok := property.Key.Data.(*js_ast.EString); ok {
|
|
p.addSourceMapping(property.Key.Loc)
|
|
p.print(helpers.UTF16ToString(str.Value))
|
|
} else {
|
|
p.print("{...{")
|
|
p.printSpace()
|
|
p.print("[")
|
|
p.printExpr(property.Key, js_ast.LComma, 0)
|
|
p.print("]:")
|
|
p.printSpace()
|
|
p.printExpr(property.ValueOrNil, js_ast.LComma, 0)
|
|
p.printSpace()
|
|
p.print("}}")
|
|
continue
|
|
}
|
|
|
|
isMultiLine := p.willPrintExprCommentsAtLoc(property.ValueOrNil.Loc)
|
|
|
|
if property.Flags.Has(js_ast.PropertyWasShorthand) {
|
|
// Implicit "true" value
|
|
if boolean, ok := property.ValueOrNil.Data.(*js_ast.EBoolean); ok && boolean.Value {
|
|
continue
|
|
}
|
|
|
|
// JSX element as JSX attribute value
|
|
if _, ok := property.ValueOrNil.Data.(*js_ast.EJSXElement); ok {
|
|
p.print("=")
|
|
p.printExpr(property.ValueOrNil, js_ast.LLowest, 0)
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Special-case raw text
|
|
if text, ok := property.ValueOrNil.Data.(*js_ast.EJSXText); ok {
|
|
p.print("=")
|
|
p.addSourceMapping(property.ValueOrNil.Loc)
|
|
p.print(text.Raw)
|
|
continue
|
|
}
|
|
|
|
// Generic JS value
|
|
p.print("={")
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
}
|
|
p.printExpr(property.ValueOrNil, js_ast.LComma, 0)
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
}
|
|
p.print("}")
|
|
}
|
|
|
|
// End the opening tag
|
|
if !e.IsTagSingleLine {
|
|
p.options.Indent--
|
|
if len(e.Properties) > 0 {
|
|
p.printNewline()
|
|
p.printIndent()
|
|
}
|
|
}
|
|
if e.TagOrNil.Data != nil && len(e.NullableChildren) == 0 {
|
|
if e.IsTagSingleLine || len(e.Properties) == 0 {
|
|
p.printSpace()
|
|
}
|
|
p.addSourceMapping(e.CloseLoc)
|
|
p.print("/>")
|
|
break
|
|
}
|
|
p.print(">")
|
|
|
|
// Print the children
|
|
for _, childOrNil := range e.NullableChildren {
|
|
if _, ok := childOrNil.Data.(*js_ast.EJSXElement); ok {
|
|
p.printExpr(childOrNil, js_ast.LLowest, 0)
|
|
} else if text, ok := childOrNil.Data.(*js_ast.EJSXText); ok {
|
|
p.addSourceMapping(childOrNil.Loc)
|
|
p.print(text.Raw)
|
|
} else if childOrNil.Data != nil {
|
|
isMultiLine := p.willPrintExprCommentsAtLoc(childOrNil.Loc)
|
|
p.print("{")
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
}
|
|
p.printExpr(childOrNil, js_ast.LComma, 0)
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
}
|
|
p.print("}")
|
|
} else {
|
|
p.print("{")
|
|
if p.willPrintExprCommentsAtLoc(childOrNil.Loc) {
|
|
// Note: Some people use these comments for AST transformations
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printExprCommentsAfterCloseTokenAtLoc(childOrNil.Loc)
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
}
|
|
p.print("}")
|
|
}
|
|
}
|
|
|
|
// Print the closing tag
|
|
p.addSourceMapping(e.CloseLoc)
|
|
p.print("</")
|
|
p.printJSXTag(e.TagOrNil)
|
|
p.print(">")
|
|
|
|
case *js_ast.ENew:
|
|
wrap := level >= js_ast.LCall
|
|
|
|
hasPureComment := !p.options.MinifyWhitespace && e.CanBeUnwrappedIfUnused
|
|
if hasPureComment && level >= js_ast.LPostfix {
|
|
wrap = true
|
|
}
|
|
|
|
if wrap {
|
|
p.print("(")
|
|
}
|
|
|
|
if hasPureComment {
|
|
p.addSourceMapping(expr.Loc)
|
|
p.print("/* @__PURE__ */ ")
|
|
}
|
|
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(expr.Loc)
|
|
p.print("new")
|
|
p.printSpace()
|
|
p.printExpr(e.Target, js_ast.LNew, isNewTarget)
|
|
|
|
// Omit the "()" when minifying, but only when safe to do so
|
|
isMultiLine := !p.options.MinifyWhitespace && ((e.IsMultiLine && len(e.Args) > 0) ||
|
|
p.willPrintExprCommentsForAnyOf(e.Args) ||
|
|
p.willPrintExprCommentsAtLoc(e.CloseParenLoc))
|
|
if !p.options.MinifyWhitespace || len(e.Args) > 0 || level >= js_ast.LPostfix || isMultiLine {
|
|
needsNewline := true
|
|
p.print("(")
|
|
if isMultiLine {
|
|
p.options.Indent++
|
|
}
|
|
for i, arg := range e.Args {
|
|
if i != 0 {
|
|
p.print(",")
|
|
}
|
|
if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() {
|
|
if isMultiLine {
|
|
if needsNewline {
|
|
p.printNewline()
|
|
}
|
|
p.printIndent()
|
|
} else if i != 0 {
|
|
p.printSpace()
|
|
}
|
|
}
|
|
p.printExpr(arg, js_ast.LComma, 0)
|
|
needsNewline = true
|
|
}
|
|
if isMultiLine {
|
|
if needsNewline || p.willPrintExprCommentsAtLoc(e.CloseParenLoc) {
|
|
p.printNewline()
|
|
}
|
|
p.printExprCommentsAfterCloseTokenAtLoc(e.CloseParenLoc)
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
}
|
|
if e.CloseParenLoc.Start > expr.Loc.Start {
|
|
p.addSourceMapping(e.CloseParenLoc)
|
|
}
|
|
p.print(")")
|
|
}
|
|
|
|
if wrap {
|
|
p.print(")")
|
|
}
|
|
|
|
case *js_ast.ECall:
|
|
if p.options.MinifySyntax {
|
|
var symbolFlags ast.SymbolFlags
|
|
switch target := e.Target.Data.(type) {
|
|
case *js_ast.EIdentifier:
|
|
symbolFlags = p.symbols.Get(target.Ref).Flags
|
|
case *js_ast.EImportIdentifier:
|
|
ref := ast.FollowSymbols(p.symbols, target.Ref)
|
|
symbolFlags = p.symbols.Get(ref).Flags
|
|
}
|
|
|
|
// Replace non-mutated empty functions with their arguments at print time
|
|
if (symbolFlags & (ast.IsEmptyFunction | ast.CouldPotentiallyBeMutated)) == ast.IsEmptyFunction {
|
|
var replacement js_ast.Expr
|
|
for _, arg := range e.Args {
|
|
if _, ok := arg.Data.(*js_ast.ESpread); ok {
|
|
arg.Data = &js_ast.EArray{Items: []js_ast.Expr{arg}, IsSingleLine: true}
|
|
}
|
|
replacement = js_ast.JoinWithComma(replacement, p.astHelpers.SimplifyUnusedExpr(arg, p.options.UnsupportedFeatures))
|
|
}
|
|
if replacement.Data == nil || (flags&exprResultIsUnused) == 0 {
|
|
replacement = js_ast.JoinWithComma(replacement, js_ast.Expr{Loc: expr.Loc, Data: js_ast.EUndefinedShared})
|
|
}
|
|
p.printExpr(p.guardAgainstBehaviorChangeDueToSubstitution(replacement, flags), level, flags)
|
|
break
|
|
}
|
|
|
|
// Inline non-mutated identity functions at print time
|
|
if (symbolFlags&(ast.IsIdentityFunction|ast.CouldPotentiallyBeMutated)) == ast.IsIdentityFunction && len(e.Args) == 1 {
|
|
arg := e.Args[0]
|
|
if _, ok := arg.Data.(*js_ast.ESpread); !ok {
|
|
if (flags & exprResultIsUnused) != 0 {
|
|
arg = p.astHelpers.SimplifyUnusedExpr(arg, p.options.UnsupportedFeatures)
|
|
if arg.Data == nil {
|
|
arg.Data = js_ast.EUndefinedShared
|
|
}
|
|
}
|
|
p.printExpr(p.guardAgainstBehaviorChangeDueToSubstitution(arg, flags), level, flags)
|
|
break
|
|
}
|
|
}
|
|
|
|
// Inline IIFEs that return expressions at print time
|
|
if len(e.Args) == 0 {
|
|
// Note: Do not inline async arrow functions as they are not IIFEs. In
|
|
// particular, they are not necessarily invoked immediately, and any
|
|
// exceptions involved in their evaluation will be swallowed without
|
|
// bubbling up to the surrounding context.
|
|
if arrow, ok := e.Target.Data.(*js_ast.EArrow); ok && len(arrow.Args) == 0 && !arrow.IsAsync {
|
|
stmts := arrow.Body.Block.Stmts
|
|
|
|
// "(() => {})()" => "void 0"
|
|
if len(stmts) == 0 {
|
|
value := js_ast.Expr{Loc: expr.Loc, Data: js_ast.EUndefinedShared}
|
|
p.printExpr(p.guardAgainstBehaviorChangeDueToSubstitution(value, flags), level, flags)
|
|
break
|
|
}
|
|
|
|
// "(() => 123)()" => "123"
|
|
if len(stmts) == 1 {
|
|
if stmt, ok := stmts[0].Data.(*js_ast.SReturn); ok {
|
|
value := stmt.ValueOrNil
|
|
if value.Data == nil {
|
|
value.Data = js_ast.EUndefinedShared
|
|
}
|
|
p.printExpr(p.guardAgainstBehaviorChangeDueToSubstitution(value, flags), level, flags)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
wrap := level >= js_ast.LNew || (flags&isNewTarget) != 0
|
|
var targetFlags printExprFlags
|
|
if e.OptionalChain == js_ast.OptionalChainNone {
|
|
targetFlags = hasNonOptionalChainParent
|
|
} else if (flags & hasNonOptionalChainParent) != 0 {
|
|
wrap = true
|
|
}
|
|
|
|
hasPureComment := !p.options.MinifyWhitespace && e.CanBeUnwrappedIfUnused
|
|
if hasPureComment && level >= js_ast.LPostfix {
|
|
wrap = true
|
|
}
|
|
|
|
if wrap {
|
|
p.print("(")
|
|
}
|
|
|
|
if hasPureComment {
|
|
flags := p.saveExprStartFlags()
|
|
p.addSourceMapping(expr.Loc)
|
|
p.print("/* @__PURE__ */ ")
|
|
p.restoreExprStartFlags(flags)
|
|
}
|
|
|
|
// We don't ever want to accidentally generate a direct eval expression here
|
|
p.callTarget = e.Target.Data
|
|
if (e.Kind != js_ast.DirectEval && p.isUnboundEvalIdentifier(e.Target) && e.OptionalChain == js_ast.OptionalChainNone) ||
|
|
(e.Kind != js_ast.TargetWasOriginallyPropertyAccess && js_ast.IsPropertyAccess(e.Target)) {
|
|
p.print("(0,")
|
|
p.printSpace()
|
|
p.printExpr(e.Target, js_ast.LPostfix, isCallTargetOrTemplateTag)
|
|
p.print(")")
|
|
} else {
|
|
p.printExpr(e.Target, js_ast.LPostfix, isCallTargetOrTemplateTag|targetFlags)
|
|
}
|
|
|
|
if e.OptionalChain == js_ast.OptionalChainStart {
|
|
p.print("?.")
|
|
}
|
|
|
|
isMultiLine := !p.options.MinifyWhitespace && ((e.IsMultiLine && len(e.Args) > 0) ||
|
|
p.willPrintExprCommentsForAnyOf(e.Args) ||
|
|
p.willPrintExprCommentsAtLoc(e.CloseParenLoc))
|
|
p.print("(")
|
|
if isMultiLine {
|
|
p.options.Indent++
|
|
}
|
|
for i, arg := range e.Args {
|
|
if i != 0 {
|
|
p.print(",")
|
|
}
|
|
if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() {
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.printIndent()
|
|
} else if i != 0 {
|
|
p.printSpace()
|
|
}
|
|
}
|
|
p.printExpr(arg, js_ast.LComma, 0)
|
|
}
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.printExprCommentsAfterCloseTokenAtLoc(e.CloseParenLoc)
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
}
|
|
if e.CloseParenLoc.Start > expr.Loc.Start {
|
|
p.addSourceMapping(e.CloseParenLoc)
|
|
}
|
|
p.print(")")
|
|
|
|
if wrap {
|
|
p.print(")")
|
|
}
|
|
|
|
case *js_ast.ERequireString:
|
|
p.addSourceMapping(expr.Loc)
|
|
p.printRequireOrImportExpr(e.ImportRecordIndex, level, flags, e.CloseParenLoc, ast.EvaluationPhase)
|
|
|
|
case *js_ast.ERequireResolveString:
|
|
recordLoc := p.importRecords[e.ImportRecordIndex].Range.Loc
|
|
isMultiLine := p.willPrintExprCommentsAtLoc(recordLoc) || p.willPrintExprCommentsAtLoc(e.CloseParenLoc)
|
|
wrap := level >= js_ast.LNew || (flags&isNewTarget) != 0
|
|
if wrap {
|
|
p.print("(")
|
|
}
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(expr.Loc)
|
|
p.print("require.resolve(")
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
p.printExprCommentsAtLoc(recordLoc)
|
|
}
|
|
p.printPath(e.ImportRecordIndex, ast.ImportRequireResolve)
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.printExprCommentsAfterCloseTokenAtLoc(e.CloseParenLoc)
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
}
|
|
if e.CloseParenLoc.Start > expr.Loc.Start {
|
|
p.addSourceMapping(e.CloseParenLoc)
|
|
}
|
|
p.print(")")
|
|
if wrap {
|
|
p.print(")")
|
|
}
|
|
|
|
case *js_ast.EImportString:
|
|
p.addSourceMapping(expr.Loc)
|
|
p.printRequireOrImportExpr(e.ImportRecordIndex, level, flags, e.CloseParenLoc, p.importRecords[e.ImportRecordIndex].Phase)
|
|
|
|
case *js_ast.EImportCall:
|
|
// Only print the second argument if either import assertions or import attributes are supported
|
|
printImportAssertOrWith := e.OptionsOrNil.Data != nil && (!p.options.UnsupportedFeatures.Has(compat.ImportAssertions) || !p.options.UnsupportedFeatures.Has(compat.ImportAttributes))
|
|
isMultiLine := !p.options.MinifyWhitespace &&
|
|
(p.willPrintExprCommentsAtLoc(e.Expr.Loc) ||
|
|
(printImportAssertOrWith && p.willPrintExprCommentsAtLoc(e.OptionsOrNil.Loc)) ||
|
|
p.willPrintExprCommentsAtLoc(e.CloseParenLoc))
|
|
wrap := level >= js_ast.LNew || (flags&isNewTarget) != 0
|
|
if wrap {
|
|
p.print("(")
|
|
}
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(expr.Loc)
|
|
switch e.Phase {
|
|
case ast.DeferPhase:
|
|
p.print("import.defer(")
|
|
case ast.SourcePhase:
|
|
p.print("import.source(")
|
|
default:
|
|
p.print("import(")
|
|
}
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
}
|
|
p.printExpr(e.Expr, js_ast.LComma, 0)
|
|
|
|
if printImportAssertOrWith {
|
|
p.print(",")
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.printIndent()
|
|
} else {
|
|
p.printSpace()
|
|
}
|
|
p.printExpr(e.OptionsOrNil, js_ast.LComma, 0)
|
|
}
|
|
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.printExprCommentsAfterCloseTokenAtLoc(e.CloseParenLoc)
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
}
|
|
p.print(")")
|
|
if wrap {
|
|
p.print(")")
|
|
}
|
|
|
|
case *js_ast.EDot:
|
|
wrap := false
|
|
if e.OptionalChain == js_ast.OptionalChainNone {
|
|
flags |= hasNonOptionalChainParent
|
|
|
|
// Inline cross-module TypeScript enum references here
|
|
if value, ok := p.tryToGetImportedEnumValue(e.Target, e.Name); ok {
|
|
if value.String != nil {
|
|
p.printQuotedUTF16(value.String, printQuotedAllowBacktick)
|
|
} else {
|
|
p.printNumber(value.Number, level)
|
|
}
|
|
if !p.options.MinifyWhitespace && !p.options.MinifyIdentifiers && !strings.Contains(e.Name, "*/") {
|
|
p.print(" /* ")
|
|
p.print(e.Name)
|
|
p.print(" */")
|
|
}
|
|
break
|
|
}
|
|
} else {
|
|
if (flags & (isNewTarget | hasNonOptionalChainParent)) != 0 {
|
|
wrap = true
|
|
p.print("(")
|
|
}
|
|
flags &= ^(isNewTarget | hasNonOptionalChainParent)
|
|
}
|
|
p.printExpr(e.Target, js_ast.LPostfix, (flags&(isNewTarget|hasNonOptionalChainParent))|isPropertyAccessTarget)
|
|
if p.canPrintIdentifier(e.Name) {
|
|
if e.OptionalChain != js_ast.OptionalChainStart && p.needSpaceBeforeDot == len(p.js) {
|
|
// "1.toString" is a syntax error, so print "1 .toString" instead
|
|
p.print(" ")
|
|
}
|
|
if e.OptionalChain == js_ast.OptionalChainStart {
|
|
p.print("?.")
|
|
} else {
|
|
p.print(".")
|
|
}
|
|
if p.options.LineLimit > 0 {
|
|
p.printNewlinePastLineLimit()
|
|
}
|
|
p.addSourceMapping(e.NameLoc)
|
|
p.printIdentifier(e.Name)
|
|
} else {
|
|
if e.OptionalChain == js_ast.OptionalChainStart {
|
|
p.print("?.")
|
|
}
|
|
p.print("[")
|
|
p.addSourceMapping(e.NameLoc)
|
|
p.printQuotedUTF8(e.Name, printQuotedAllowBacktick)
|
|
p.print("]")
|
|
}
|
|
if wrap {
|
|
p.print(")")
|
|
}
|
|
|
|
case *js_ast.EIndex:
|
|
if e.OptionalChain == js_ast.OptionalChainNone {
|
|
flags |= hasNonOptionalChainParent
|
|
|
|
// Inline cross-module TypeScript enum references here
|
|
if index, ok := e.Index.Data.(*js_ast.EString); ok {
|
|
if value, name, ok := p.tryToGetImportedEnumValueUTF16(e.Target, index.Value); ok {
|
|
if value.String != nil {
|
|
p.printQuotedUTF16(value.String, printQuotedAllowBacktick)
|
|
} else {
|
|
p.printNumber(value.Number, level)
|
|
}
|
|
if !p.options.MinifyWhitespace && !p.options.MinifyIdentifiers && !strings.Contains(name, "*/") {
|
|
p.print(" /* ")
|
|
p.print(name)
|
|
p.print(" */")
|
|
}
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
if (flags & (isNewTarget | hasNonOptionalChainParent)) != 0 {
|
|
p.print("(")
|
|
defer p.print(")")
|
|
}
|
|
flags &= ^(isNewTarget | hasNonOptionalChainParent)
|
|
}
|
|
p.printExpr(e.Target, js_ast.LPostfix, (flags&(isNewTarget|hasNonOptionalChainParent))|isPropertyAccessTarget)
|
|
if e.OptionalChain == js_ast.OptionalChainStart {
|
|
p.print("?.")
|
|
}
|
|
|
|
switch index := e.Index.Data.(type) {
|
|
case *js_ast.EPrivateIdentifier:
|
|
if e.OptionalChain != js_ast.OptionalChainStart {
|
|
p.print(".")
|
|
}
|
|
name := p.renamer.NameForSymbol(index.Ref)
|
|
p.addSourceMappingForName(e.Index.Loc, name, index.Ref)
|
|
p.printIdentifier(name)
|
|
return
|
|
|
|
case *js_ast.ENameOfSymbol:
|
|
if name := p.mangledPropName(index.Ref); p.canPrintIdentifier(name) {
|
|
if e.OptionalChain != js_ast.OptionalChainStart {
|
|
p.print(".")
|
|
}
|
|
p.addSourceMappingForName(e.Index.Loc, name, index.Ref)
|
|
p.printIdentifier(name)
|
|
return
|
|
}
|
|
|
|
case *js_ast.EInlinedEnum:
|
|
if p.options.MinifySyntax {
|
|
if str, ok := index.Value.Data.(*js_ast.EString); ok && p.canPrintIdentifierUTF16(str.Value) {
|
|
if e.OptionalChain != js_ast.OptionalChainStart {
|
|
p.print(".")
|
|
}
|
|
p.addSourceMapping(index.Value.Loc)
|
|
p.printIdentifierUTF16(str.Value)
|
|
return
|
|
}
|
|
}
|
|
|
|
case *js_ast.EDot:
|
|
if p.options.MinifySyntax {
|
|
if value, ok := p.tryToGetImportedEnumValue(index.Target, index.Name); ok && value.String != nil && p.canPrintIdentifierUTF16(value.String) {
|
|
if e.OptionalChain != js_ast.OptionalChainStart {
|
|
p.print(".")
|
|
}
|
|
p.addSourceMapping(e.Index.Loc)
|
|
p.printIdentifierUTF16(value.String)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
isMultiLine := p.willPrintExprCommentsAtLoc(e.Index.Loc) || p.willPrintExprCommentsAtLoc(e.CloseBracketLoc)
|
|
p.print("[")
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
}
|
|
p.printExpr(e.Index, js_ast.LLowest, 0)
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.printExprCommentsAfterCloseTokenAtLoc(e.CloseBracketLoc)
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
}
|
|
if e.CloseBracketLoc.Start > expr.Loc.Start {
|
|
p.addSourceMapping(e.CloseBracketLoc)
|
|
}
|
|
p.print("]")
|
|
|
|
case *js_ast.EIf:
|
|
wrap := level >= js_ast.LConditional
|
|
if wrap {
|
|
p.print("(")
|
|
flags &= ^forbidIn
|
|
}
|
|
p.printExpr(e.Test, js_ast.LConditional, (flags&forbidIn)|parentWasUnaryOrBinaryOrIfTest)
|
|
p.printSpace()
|
|
p.print("?")
|
|
if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() {
|
|
p.printSpace()
|
|
}
|
|
p.printExprWithoutLeadingNewline(e.Yes, js_ast.LYield, 0)
|
|
p.printSpace()
|
|
p.print(":")
|
|
if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() {
|
|
p.printSpace()
|
|
}
|
|
p.printExprWithoutLeadingNewline(e.No, js_ast.LYield, flags&forbidIn)
|
|
if wrap {
|
|
p.print(")")
|
|
}
|
|
|
|
case *js_ast.EArrow:
|
|
wrap := e.IsParenthesized || level >= js_ast.LAssign
|
|
|
|
if wrap {
|
|
p.print("(")
|
|
}
|
|
if !p.options.MinifyWhitespace && e.HasNoSideEffectsComment {
|
|
p.print("/* @__NO_SIDE_EFFECTS__ */ ")
|
|
}
|
|
if e.IsAsync {
|
|
p.addSourceMapping(expr.Loc)
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("async")
|
|
p.printSpace()
|
|
}
|
|
|
|
p.printFnArgs(e.Args, fnArgsOpts{
|
|
openParenLoc: expr.Loc,
|
|
addMappingForOpenParenLoc: !e.IsAsync,
|
|
hasRestArg: e.HasRestArg,
|
|
isArrow: true,
|
|
})
|
|
p.printSpace()
|
|
p.print("=>")
|
|
p.printSpace()
|
|
|
|
wasPrinted := false
|
|
if len(e.Body.Block.Stmts) == 1 && e.PreferExpr {
|
|
if s, ok := e.Body.Block.Stmts[0].Data.(*js_ast.SReturn); ok && s.ValueOrNil.Data != nil {
|
|
var nestedFlags printExprFlags
|
|
if (flags&forbidIn) != 0 && !wrap {
|
|
nestedFlags |= forbidIn
|
|
}
|
|
p.arrowExprStart = len(p.js)
|
|
p.printExprWithoutLeadingNewline(s.ValueOrNil, js_ast.LComma, nestedFlags)
|
|
wasPrinted = true
|
|
}
|
|
}
|
|
if !wasPrinted {
|
|
p.printBlock(e.Body.Loc, e.Body.Block)
|
|
}
|
|
if wrap {
|
|
p.print(")")
|
|
}
|
|
|
|
case *js_ast.EFunction:
|
|
n := len(p.js)
|
|
wrap := e.IsParenthesized || p.stmtStart == n || p.exportDefaultStart == n ||
|
|
((flags&isPropertyAccessTarget) != 0 && p.options.UnsupportedFeatures.Has(compat.FunctionOrClassPropertyAccess))
|
|
if wrap {
|
|
p.print("(")
|
|
}
|
|
if !p.options.MinifyWhitespace && e.Fn.HasNoSideEffectsComment {
|
|
p.print("/* @__NO_SIDE_EFFECTS__ */ ")
|
|
}
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(expr.Loc)
|
|
if e.Fn.IsAsync {
|
|
p.print("async ")
|
|
}
|
|
p.print("function")
|
|
if e.Fn.IsGenerator {
|
|
p.print("*")
|
|
p.printSpace()
|
|
}
|
|
if e.Fn.Name != nil {
|
|
p.printSpaceBeforeIdentifier()
|
|
name := p.renamer.NameForSymbol(e.Fn.Name.Ref)
|
|
p.addSourceMappingForName(e.Fn.Name.Loc, name, e.Fn.Name.Ref)
|
|
p.printIdentifier(name)
|
|
}
|
|
p.printFn(e.Fn)
|
|
if wrap {
|
|
p.print(")")
|
|
}
|
|
|
|
case *js_ast.EClass:
|
|
n := len(p.js)
|
|
wrap := p.stmtStart == n || p.exportDefaultStart == n ||
|
|
((flags&isPropertyAccessTarget) != 0 && p.options.UnsupportedFeatures.Has(compat.FunctionOrClassPropertyAccess))
|
|
if wrap {
|
|
p.print("(")
|
|
}
|
|
p.printDecorators(e.Class.Decorators, printSpaceAfterDecorator)
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(expr.Loc)
|
|
p.print("class")
|
|
if e.Class.Name != nil {
|
|
p.print(" ")
|
|
name := p.renamer.NameForSymbol(e.Class.Name.Ref)
|
|
p.addSourceMappingForName(e.Class.Name.Loc, name, e.Class.Name.Ref)
|
|
p.printIdentifier(name)
|
|
}
|
|
p.printClass(e.Class)
|
|
if wrap {
|
|
p.print(")")
|
|
}
|
|
|
|
case *js_ast.EArray:
|
|
isMultiLine := (len(e.Items) > 0 && !e.IsSingleLine) || p.willPrintExprCommentsForAnyOf(e.Items) || p.willPrintExprCommentsAtLoc(e.CloseBracketLoc)
|
|
p.addSourceMapping(expr.Loc)
|
|
p.print("[")
|
|
if len(e.Items) > 0 || isMultiLine {
|
|
if isMultiLine {
|
|
p.options.Indent++
|
|
}
|
|
|
|
for i, item := range e.Items {
|
|
if i != 0 {
|
|
p.print(",")
|
|
}
|
|
if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() {
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.printIndent()
|
|
} else if i != 0 {
|
|
p.printSpace()
|
|
}
|
|
}
|
|
p.printExpr(item, js_ast.LComma, 0)
|
|
|
|
// Make sure there's a comma after trailing missing items
|
|
_, ok := item.Data.(*js_ast.EMissing)
|
|
if ok && i == len(e.Items)-1 {
|
|
p.print(",")
|
|
}
|
|
}
|
|
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.printExprCommentsAfterCloseTokenAtLoc(e.CloseBracketLoc)
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
}
|
|
}
|
|
if e.CloseBracketLoc.Start > expr.Loc.Start {
|
|
p.addSourceMapping(e.CloseBracketLoc)
|
|
}
|
|
p.print("]")
|
|
|
|
case *js_ast.EObject:
|
|
isMultiLine := (len(e.Properties) > 0 && !e.IsSingleLine) || p.willPrintExprCommentsAtLoc(e.CloseBraceLoc)
|
|
if !p.options.MinifyWhitespace && !isMultiLine {
|
|
for _, property := range e.Properties {
|
|
if p.willPrintExprCommentsAtLoc(property.Loc) {
|
|
isMultiLine = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
n := len(p.js)
|
|
wrap := p.stmtStart == n || p.arrowExprStart == n
|
|
if wrap {
|
|
p.print("(")
|
|
}
|
|
p.addSourceMapping(expr.Loc)
|
|
p.print("{")
|
|
if len(e.Properties) > 0 || isMultiLine {
|
|
if isMultiLine {
|
|
p.options.Indent++
|
|
}
|
|
|
|
for i, item := range e.Properties {
|
|
if i != 0 {
|
|
p.print(",")
|
|
}
|
|
if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() {
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.printIndent()
|
|
} else {
|
|
p.printSpace()
|
|
}
|
|
}
|
|
p.printProperty(item)
|
|
}
|
|
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.printExprCommentsAfterCloseTokenAtLoc(e.CloseBraceLoc)
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
} else if len(e.Properties) > 0 {
|
|
p.printSpace()
|
|
}
|
|
}
|
|
if e.CloseBraceLoc.Start > expr.Loc.Start {
|
|
p.addSourceMapping(e.CloseBraceLoc)
|
|
}
|
|
p.print("}")
|
|
if wrap {
|
|
p.print(")")
|
|
}
|
|
|
|
case *js_ast.EBoolean:
|
|
p.addSourceMapping(expr.Loc)
|
|
if p.options.MinifySyntax {
|
|
if level >= js_ast.LPrefix {
|
|
if e.Value {
|
|
p.print("(!0)")
|
|
} else {
|
|
p.print("(!1)")
|
|
}
|
|
} else {
|
|
if e.Value {
|
|
p.print("!0")
|
|
} else {
|
|
p.print("!1")
|
|
}
|
|
}
|
|
} else {
|
|
p.printSpaceBeforeIdentifier()
|
|
if e.Value {
|
|
p.print("true")
|
|
} else {
|
|
p.print("false")
|
|
}
|
|
}
|
|
|
|
case *js_ast.EString:
|
|
var flags printQuotedFlags
|
|
if e.ContainsUniqueKey {
|
|
flags = printQuotedNoWrap
|
|
}
|
|
p.addSourceMapping(expr.Loc)
|
|
|
|
if !p.options.MinifyWhitespace && e.HasPropertyKeyComment {
|
|
p.print("/* @__KEY__ */ ")
|
|
}
|
|
|
|
// If this was originally a template literal, print it as one as long as we're not minifying
|
|
if e.PreferTemplate && !p.options.MinifySyntax && !p.options.UnsupportedFeatures.Has(compat.TemplateLiteral) {
|
|
p.print("`")
|
|
p.printUnquotedUTF16(e.Value, '`', flags)
|
|
p.print("`")
|
|
return
|
|
}
|
|
|
|
p.printQuotedUTF16(e.Value, flags|printQuotedAllowBacktick)
|
|
|
|
case *js_ast.ETemplate:
|
|
if e.TagOrNil.Data == nil && (p.options.MinifySyntax || p.wasLazyExport) {
|
|
// Inline enums and mangled properties when minifying
|
|
var replaced []js_ast.TemplatePart
|
|
for i, part := range e.Parts {
|
|
var inlinedValue js_ast.E
|
|
switch e2 := part.Value.Data.(type) {
|
|
case *js_ast.ENameOfSymbol:
|
|
inlinedValue = &js_ast.EString{
|
|
Value: helpers.StringToUTF16(p.mangledPropName(e2.Ref)),
|
|
HasPropertyKeyComment: e2.HasPropertyKeyComment,
|
|
}
|
|
case *js_ast.EDot:
|
|
if value, ok := p.tryToGetImportedEnumValue(e2.Target, e2.Name); ok {
|
|
if value.String != nil {
|
|
inlinedValue = &js_ast.EString{Value: value.String}
|
|
} else {
|
|
inlinedValue = &js_ast.ENumber{Value: value.Number}
|
|
}
|
|
}
|
|
}
|
|
if inlinedValue != nil {
|
|
if replaced == nil {
|
|
replaced = make([]js_ast.TemplatePart, 0, len(e.Parts))
|
|
replaced = append(replaced, e.Parts[:i]...)
|
|
}
|
|
part.Value.Data = inlinedValue
|
|
replaced = append(replaced, part)
|
|
} else if replaced != nil {
|
|
replaced = append(replaced, part)
|
|
}
|
|
}
|
|
if replaced != nil {
|
|
copy := *e
|
|
copy.Parts = replaced
|
|
switch e2 := js_ast.InlinePrimitivesIntoTemplate(logger.Loc{}, ©).Data.(type) {
|
|
case *js_ast.EString:
|
|
p.printQuotedUTF16(e2.Value, printQuotedAllowBacktick)
|
|
return
|
|
case *js_ast.ETemplate:
|
|
e = e2
|
|
}
|
|
}
|
|
|
|
// Convert no-substitution template literals into strings if it's smaller
|
|
if len(e.Parts) == 0 {
|
|
p.addSourceMapping(expr.Loc)
|
|
p.printQuotedUTF16(e.HeadCooked, printQuotedAllowBacktick)
|
|
return
|
|
}
|
|
}
|
|
|
|
if e.TagOrNil.Data != nil {
|
|
tagIsPropertyAccess := false
|
|
switch e.TagOrNil.Data.(type) {
|
|
case *js_ast.EDot, *js_ast.EIndex:
|
|
tagIsPropertyAccess = true
|
|
}
|
|
if !e.TagWasOriginallyPropertyAccess && tagIsPropertyAccess {
|
|
// Prevent "x``" from becoming "y.z``"
|
|
p.print("(0,")
|
|
p.printSpace()
|
|
p.printExpr(e.TagOrNil, js_ast.LLowest, isCallTargetOrTemplateTag)
|
|
p.print(")")
|
|
} else if js_ast.IsOptionalChain(e.TagOrNil) {
|
|
// Optional chains are forbidden in template tags
|
|
p.print("(")
|
|
p.printExpr(e.TagOrNil, js_ast.LLowest, isCallTargetOrTemplateTag)
|
|
p.print(")")
|
|
} else {
|
|
p.printExpr(e.TagOrNil, js_ast.LPostfix, isCallTargetOrTemplateTag|(flags&isNewTarget))
|
|
}
|
|
} else {
|
|
p.addSourceMapping(expr.Loc)
|
|
}
|
|
p.print("`")
|
|
if e.TagOrNil.Data != nil {
|
|
p.print(e.HeadRaw)
|
|
} else {
|
|
p.printUnquotedUTF16(e.HeadCooked, '`', 0)
|
|
}
|
|
for _, part := range e.Parts {
|
|
p.print("${")
|
|
p.printExpr(part.Value, js_ast.LLowest, 0)
|
|
p.addSourceMapping(part.TailLoc)
|
|
p.print("}")
|
|
if e.TagOrNil.Data != nil {
|
|
p.print(part.TailRaw)
|
|
} else {
|
|
p.printUnquotedUTF16(part.TailCooked, '`', 0)
|
|
}
|
|
}
|
|
p.print("`")
|
|
|
|
case *js_ast.ERegExp:
|
|
buffer := p.js
|
|
n := len(buffer)
|
|
|
|
// Avoid forming a single-line comment or "</script" sequence
|
|
if !p.options.UnsupportedFeatures.Has(compat.InlineScript) && n > 0 {
|
|
if last := buffer[n-1]; last == '/' || (last == '<' && len(e.Value) >= 7 && strings.EqualFold(e.Value[:7], "/script")) {
|
|
p.print(" ")
|
|
}
|
|
}
|
|
|
|
p.addSourceMapping(expr.Loc)
|
|
p.print(e.Value)
|
|
|
|
// Need a space before the next identifier to avoid it turning into flags
|
|
p.prevRegExpEnd = len(p.js)
|
|
|
|
case *js_ast.EInlinedEnum:
|
|
p.printExpr(e.Value, level, flags)
|
|
|
|
if !p.options.MinifyWhitespace && !p.options.MinifyIdentifiers {
|
|
p.print(" /* ")
|
|
p.print(e.Comment)
|
|
p.print(" */")
|
|
}
|
|
|
|
case *js_ast.EBigInt:
|
|
if !p.options.UnsupportedFeatures.Has(compat.Bigint) {
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(expr.Loc)
|
|
p.print(e.Value)
|
|
p.print("n")
|
|
break
|
|
}
|
|
|
|
wrap := level >= js_ast.LNew || (flags&isNewTarget) != 0
|
|
hasPureComment := !p.options.MinifyWhitespace
|
|
|
|
if hasPureComment && level >= js_ast.LPostfix {
|
|
wrap = true
|
|
}
|
|
|
|
if wrap {
|
|
p.print("(")
|
|
}
|
|
|
|
if hasPureComment {
|
|
flags := p.saveExprStartFlags()
|
|
p.addSourceMapping(expr.Loc)
|
|
p.print("/* @__PURE__ */ ")
|
|
p.restoreExprStartFlags(flags)
|
|
}
|
|
|
|
value := e.Value
|
|
useQuotes := true
|
|
|
|
// When minifying, try to convert to a shorter form
|
|
if p.options.MinifySyntax {
|
|
var i big.Int
|
|
fmt.Sscan(value, &i)
|
|
str := i.String()
|
|
|
|
// Print without quotes if it can be converted exactly
|
|
if num, err := strconv.ParseFloat(str, 64); err == nil && str == fmt.Sprintf("%.0f", num) {
|
|
useQuotes = false
|
|
}
|
|
|
|
// Print the converted form if it's shorter (long hex strings may not be shorter)
|
|
if len(str) < len(value) {
|
|
value = str
|
|
}
|
|
}
|
|
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(expr.Loc)
|
|
|
|
if useQuotes {
|
|
p.print("BigInt(\"")
|
|
} else {
|
|
p.print("BigInt(")
|
|
}
|
|
|
|
p.print(value)
|
|
|
|
if useQuotes {
|
|
p.print("\")")
|
|
} else {
|
|
p.print(")")
|
|
}
|
|
|
|
if wrap {
|
|
p.print(")")
|
|
}
|
|
|
|
case *js_ast.ENumber:
|
|
p.addSourceMapping(expr.Loc)
|
|
p.printNumber(e.Value, level)
|
|
|
|
case *js_ast.EIdentifier:
|
|
name := p.renamer.NameForSymbol(e.Ref)
|
|
wrap := len(p.js) == p.forOfInitStart && (name == "let" ||
|
|
((flags&isFollowedByOf) != 0 && (flags&isInsideForAwait) == 0 && name == "async"))
|
|
|
|
if wrap {
|
|
p.print("(")
|
|
}
|
|
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMappingForName(expr.Loc, name, e.Ref)
|
|
p.printIdentifier(name)
|
|
|
|
if wrap {
|
|
p.print(")")
|
|
}
|
|
|
|
case *js_ast.EImportIdentifier:
|
|
// Potentially use a property access instead of an identifier
|
|
ref := ast.FollowSymbols(p.symbols, e.Ref)
|
|
symbol := p.symbols.Get(ref)
|
|
|
|
if symbol.ImportItemStatus == ast.ImportItemMissing {
|
|
p.printUndefined(expr.Loc, level)
|
|
} else if symbol.NamespaceAlias != nil {
|
|
wrap := p.callTarget == e && e.WasOriginallyIdentifier
|
|
if wrap {
|
|
p.print("(0,")
|
|
p.printSpace()
|
|
}
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(expr.Loc)
|
|
p.printIdentifier(p.renamer.NameForSymbol(symbol.NamespaceAlias.NamespaceRef))
|
|
alias := symbol.NamespaceAlias.Alias
|
|
if !e.PreferQuotedKey && p.canPrintIdentifier(alias) {
|
|
p.print(".")
|
|
p.addSourceMappingForName(expr.Loc, alias, ref)
|
|
p.printIdentifier(alias)
|
|
} else {
|
|
p.print("[")
|
|
p.addSourceMappingForName(expr.Loc, alias, ref)
|
|
p.printQuotedUTF8(alias, printQuotedAllowBacktick)
|
|
p.print("]")
|
|
}
|
|
if wrap {
|
|
p.print(")")
|
|
}
|
|
} else if value := p.options.ConstValues[ref]; value.Kind != js_ast.ConstValueNone {
|
|
// Handle inlined constants
|
|
p.printExpr(js_ast.ConstValueToExpr(expr.Loc, value), level, flags)
|
|
} else {
|
|
p.printSpaceBeforeIdentifier()
|
|
name := p.renamer.NameForSymbol(ref)
|
|
p.addSourceMappingForName(expr.Loc, name, ref)
|
|
p.printIdentifier(name)
|
|
}
|
|
|
|
case *js_ast.EAwait:
|
|
wrap := level >= js_ast.LPrefix
|
|
|
|
if wrap {
|
|
p.print("(")
|
|
}
|
|
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(expr.Loc)
|
|
p.print("await")
|
|
p.printSpace()
|
|
p.printExpr(e.Value, js_ast.LPrefix-1, 0)
|
|
|
|
if wrap {
|
|
p.print(")")
|
|
}
|
|
|
|
case *js_ast.EYield:
|
|
wrap := level >= js_ast.LAssign
|
|
|
|
if wrap {
|
|
p.print("(")
|
|
}
|
|
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(expr.Loc)
|
|
p.print("yield")
|
|
|
|
if e.ValueOrNil.Data != nil {
|
|
if e.IsStar {
|
|
p.print("*")
|
|
}
|
|
p.printSpace()
|
|
p.printExprWithoutLeadingNewline(e.ValueOrNil, js_ast.LYield, 0)
|
|
}
|
|
|
|
if wrap {
|
|
p.print(")")
|
|
}
|
|
|
|
case *js_ast.EUnary:
|
|
entry := js_ast.OpTable[e.Op]
|
|
wrap := level >= entry.Level
|
|
|
|
if wrap {
|
|
p.print("(")
|
|
}
|
|
|
|
if !e.Op.IsPrefix() {
|
|
p.printExpr(e.Value, js_ast.LPostfix-1, parentWasUnaryOrBinaryOrIfTest)
|
|
}
|
|
|
|
if entry.IsKeyword {
|
|
p.printSpaceBeforeIdentifier()
|
|
if e.Op.IsPrefix() {
|
|
p.addSourceMapping(expr.Loc)
|
|
}
|
|
p.print(entry.Text)
|
|
p.printSpace()
|
|
} else {
|
|
p.printSpaceBeforeOperator(e.Op)
|
|
if e.Op.IsPrefix() {
|
|
p.addSourceMapping(expr.Loc)
|
|
}
|
|
p.print(entry.Text)
|
|
p.prevOp = e.Op
|
|
p.prevOpEnd = len(p.js)
|
|
}
|
|
|
|
if e.Op.IsPrefix() {
|
|
valueFlags := parentWasUnaryOrBinaryOrIfTest
|
|
if e.Op == js_ast.UnOpDelete {
|
|
valueFlags |= isDeleteTarget
|
|
}
|
|
|
|
// Never turn "typeof (0, x)" into "typeof x" or "delete (0, x)" into "delete x"
|
|
if (e.Op == js_ast.UnOpTypeof && !e.WasOriginallyTypeofIdentifier && p.isUnboundIdentifier(e.Value)) ||
|
|
(e.Op == js_ast.UnOpDelete && !e.WasOriginallyDeleteOfIdentifierOrPropertyAccess && p.isIdentifierOrNumericConstantOrPropertyAccess(e.Value)) {
|
|
p.print("(0,")
|
|
p.printSpace()
|
|
p.printExpr(e.Value, js_ast.LPrefix-1, valueFlags)
|
|
p.print(")")
|
|
} else {
|
|
p.printExpr(e.Value, js_ast.LPrefix-1, valueFlags)
|
|
}
|
|
}
|
|
|
|
if wrap {
|
|
p.print(")")
|
|
}
|
|
|
|
case *js_ast.EBinary:
|
|
// The handling of binary expressions is convoluted because we're using
|
|
// iteration on the heap instead of recursion on the call stack to avoid
|
|
// stack overflow for deeply-nested ASTs. See the comments for the similar
|
|
// code in the JavaScript parser for details.
|
|
v := binaryExprVisitor{
|
|
e: e,
|
|
level: level,
|
|
flags: flags,
|
|
}
|
|
|
|
// Use a single stack to reduce allocation overhead
|
|
stackBottom := len(p.binaryExprStack)
|
|
|
|
for {
|
|
// Check whether this node is a special case, and stop if it is
|
|
if !v.checkAndPrepare(p) {
|
|
break
|
|
}
|
|
|
|
left := v.e.Left
|
|
leftBinary, ok := left.Data.(*js_ast.EBinary)
|
|
|
|
// Stop iterating if iteration doesn't apply to the left node
|
|
if !ok {
|
|
p.printExpr(left, v.leftLevel, v.leftFlags)
|
|
v.visitRightAndFinish(p)
|
|
break
|
|
}
|
|
|
|
// Manually run the code at the start of "printExpr"
|
|
p.printExprCommentsAtLoc(left.Loc)
|
|
|
|
// Only allocate heap memory on the stack for nested binary expressions
|
|
p.binaryExprStack = append(p.binaryExprStack, v)
|
|
v = binaryExprVisitor{
|
|
e: leftBinary,
|
|
level: v.leftLevel,
|
|
flags: v.leftFlags,
|
|
}
|
|
}
|
|
|
|
// Process all binary operations from the deepest-visited node back toward
|
|
// our original top-level binary operation
|
|
for {
|
|
n := len(p.binaryExprStack) - 1
|
|
if n < stackBottom {
|
|
break
|
|
}
|
|
v := p.binaryExprStack[n]
|
|
p.binaryExprStack = p.binaryExprStack[:n]
|
|
v.visitRightAndFinish(p)
|
|
}
|
|
|
|
default:
|
|
panic(fmt.Sprintf("Unexpected expression of type %T", expr.Data))
|
|
}
|
|
}
|
|
|
|
// The handling of binary expressions is convoluted because we're using
|
|
// iteration on the heap instead of recursion on the call stack to avoid
|
|
// stack overflow for deeply-nested ASTs. See the comments for the similar
|
|
// code in the JavaScript parser for details.
|
|
type binaryExprVisitor struct {
|
|
// Inputs
|
|
e *js_ast.EBinary
|
|
level js_ast.L
|
|
flags printExprFlags
|
|
|
|
// Input for visiting the left child
|
|
leftLevel js_ast.L
|
|
leftFlags printExprFlags
|
|
|
|
// "Local variables" passed from "checkAndPrepare" to "visitRightAndFinish"
|
|
entry js_ast.OpTableEntry
|
|
wrap bool
|
|
rightLevel js_ast.L
|
|
}
|
|
|
|
func (v *binaryExprVisitor) checkAndPrepare(p *printer) bool {
|
|
e := v.e
|
|
|
|
// If this is a comma operator then either the result is unused (and we
|
|
// should have already simplified unused expressions), or the result is used
|
|
// (and we can still simplify unused expressions inside the left operand)
|
|
if e.Op == js_ast.BinOpComma {
|
|
if (v.flags & didAlreadySimplifyUnusedExprs) == 0 {
|
|
left := p.simplifyUnusedExpr(e.Left)
|
|
right := e.Right
|
|
if (v.flags & exprResultIsUnused) != 0 {
|
|
right = p.simplifyUnusedExpr(right)
|
|
}
|
|
if left.Data != e.Left.Data || right.Data != e.Right.Data {
|
|
// Pass a flag so we don't needlessly re-simplify the same expression
|
|
p.printExpr(p.guardAgainstBehaviorChangeDueToSubstitution(js_ast.JoinWithComma(left, right), v.flags), v.level, v.flags|didAlreadySimplifyUnusedExprs)
|
|
return false
|
|
}
|
|
} else {
|
|
// Pass a flag so we don't needlessly re-simplify the same expression
|
|
v.flags |= didAlreadySimplifyUnusedExprs
|
|
}
|
|
}
|
|
|
|
v.entry = js_ast.OpTable[e.Op]
|
|
v.wrap = v.level >= v.entry.Level || (e.Op == js_ast.BinOpIn && (v.flags&forbidIn) != 0)
|
|
|
|
// Destructuring assignments must be parenthesized
|
|
if n := len(p.js); p.stmtStart == n || p.arrowExprStart == n {
|
|
if _, ok := e.Left.Data.(*js_ast.EObject); ok {
|
|
v.wrap = true
|
|
}
|
|
}
|
|
|
|
if v.wrap {
|
|
p.print("(")
|
|
v.flags &= ^forbidIn
|
|
}
|
|
|
|
v.leftLevel = v.entry.Level - 1
|
|
v.rightLevel = v.entry.Level - 1
|
|
|
|
if e.Op.IsRightAssociative() {
|
|
v.leftLevel = v.entry.Level
|
|
}
|
|
if e.Op.IsLeftAssociative() {
|
|
v.rightLevel = v.entry.Level
|
|
}
|
|
|
|
switch e.Op {
|
|
case js_ast.BinOpNullishCoalescing:
|
|
// "??" can't directly contain "||" or "&&" without being wrapped in parentheses
|
|
if left, ok := e.Left.Data.(*js_ast.EBinary); ok && (left.Op == js_ast.BinOpLogicalOr || left.Op == js_ast.BinOpLogicalAnd) {
|
|
v.leftLevel = js_ast.LPrefix
|
|
}
|
|
if right, ok := e.Right.Data.(*js_ast.EBinary); ok && (right.Op == js_ast.BinOpLogicalOr || right.Op == js_ast.BinOpLogicalAnd) {
|
|
v.rightLevel = js_ast.LPrefix
|
|
}
|
|
|
|
case js_ast.BinOpPow:
|
|
// "**" can't contain certain unary expressions
|
|
if left, ok := e.Left.Data.(*js_ast.EUnary); ok && left.Op.UnaryAssignTarget() == js_ast.AssignTargetNone {
|
|
v.leftLevel = js_ast.LCall
|
|
} else if _, ok := e.Left.Data.(*js_ast.EAwait); ok {
|
|
v.leftLevel = js_ast.LCall
|
|
} else if _, ok := e.Left.Data.(*js_ast.EUndefined); ok {
|
|
// Undefined is printed as "void 0"
|
|
v.leftLevel = js_ast.LCall
|
|
} else if _, ok := e.Left.Data.(*js_ast.ENumber); ok {
|
|
// Negative numbers are printed using a unary operator
|
|
v.leftLevel = js_ast.LCall
|
|
} else if p.options.MinifySyntax {
|
|
// When minifying, booleans are printed as "!0 and "!1"
|
|
if _, ok := e.Left.Data.(*js_ast.EBoolean); ok {
|
|
v.leftLevel = js_ast.LCall
|
|
}
|
|
}
|
|
}
|
|
|
|
// Special-case "#foo in bar"
|
|
if private, ok := e.Left.Data.(*js_ast.EPrivateIdentifier); ok && e.Op == js_ast.BinOpIn {
|
|
name := p.renamer.NameForSymbol(private.Ref)
|
|
p.addSourceMappingForName(e.Left.Loc, name, private.Ref)
|
|
p.printIdentifier(name)
|
|
v.visitRightAndFinish(p)
|
|
return false
|
|
}
|
|
|
|
if e.Op == js_ast.BinOpComma {
|
|
// The result of the left operand of the comma operator is unused
|
|
v.leftFlags = (v.flags & forbidIn) | exprResultIsUnused | parentWasUnaryOrBinaryOrIfTest
|
|
} else {
|
|
v.leftFlags = (v.flags & forbidIn) | parentWasUnaryOrBinaryOrIfTest
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (v *binaryExprVisitor) visitRightAndFinish(p *printer) {
|
|
e := v.e
|
|
|
|
if e.Op != js_ast.BinOpComma {
|
|
p.printSpace()
|
|
}
|
|
|
|
if v.entry.IsKeyword {
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print(v.entry.Text)
|
|
} else {
|
|
p.printSpaceBeforeOperator(e.Op)
|
|
p.print(v.entry.Text)
|
|
p.prevOp = e.Op
|
|
p.prevOpEnd = len(p.js)
|
|
}
|
|
|
|
if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() {
|
|
p.printSpace()
|
|
}
|
|
|
|
if e.Op == js_ast.BinOpComma {
|
|
// The result of the right operand of the comma operator is unused if the caller doesn't use it
|
|
p.printExpr(e.Right, v.rightLevel, (v.flags&(forbidIn|exprResultIsUnused))|parentWasUnaryOrBinaryOrIfTest)
|
|
} else {
|
|
p.printExpr(e.Right, v.rightLevel, (v.flags&forbidIn)|parentWasUnaryOrBinaryOrIfTest)
|
|
}
|
|
|
|
if v.wrap {
|
|
p.print(")")
|
|
}
|
|
}
|
|
|
|
func (p *printer) isUnboundEvalIdentifier(value js_ast.Expr) bool {
|
|
if id, ok := value.Data.(*js_ast.EIdentifier); ok {
|
|
// Using the original name here is ok since unbound symbols are not renamed
|
|
symbol := p.symbols.Get(ast.FollowSymbols(p.symbols, id.Ref))
|
|
return symbol.Kind == ast.SymbolUnbound && symbol.OriginalName == "eval"
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Convert an integer to a byte slice without any allocations
|
|
func (p *printer) smallIntToBytes(n int) []byte {
|
|
wasNegative := n < 0
|
|
if wasNegative {
|
|
// This assumes that -math.MinInt isn't a problem. This is fine because
|
|
// these integers are floating-point exponents which never go up that high.
|
|
n = -n
|
|
}
|
|
|
|
bytes := p.intToBytesBuffer[:]
|
|
start := len(bytes)
|
|
|
|
// Write out the number from the end to the front
|
|
for {
|
|
start--
|
|
bytes[start] = '0' + byte(n%10)
|
|
n /= 10
|
|
if n == 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
// Stick a negative sign on the front if needed
|
|
if wasNegative {
|
|
start--
|
|
bytes[start] = '-'
|
|
}
|
|
|
|
return bytes[start:]
|
|
}
|
|
|
|
func parseSmallInt(bytes []byte) int {
|
|
wasNegative := bytes[0] == '-'
|
|
if wasNegative {
|
|
bytes = bytes[1:]
|
|
}
|
|
|
|
// Parse the integer without any error checking. This doesn't need to handle
|
|
// integer overflow because these integers are floating-point exponents which
|
|
// never go up that high.
|
|
n := 0
|
|
for _, c := range bytes {
|
|
n = n*10 + int(c-'0')
|
|
}
|
|
|
|
if wasNegative {
|
|
return -n
|
|
}
|
|
return n
|
|
}
|
|
|
|
func (p *printer) printNonNegativeFloat(absValue float64) {
|
|
// We can avoid the slow call to strconv.FormatFloat() for integers less than
|
|
// 1000 because we know that exponential notation will always be longer than
|
|
// the integer representation. This is not the case for 1000 which is "1e3".
|
|
if absValue < 1000 {
|
|
if asInt := int64(absValue); absValue == float64(asInt) {
|
|
p.printBytes(p.smallIntToBytes(int(asInt)))
|
|
|
|
// Integers always need a space before "." to avoid making a decimal point
|
|
p.needSpaceBeforeDot = len(p.js)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Format this number into a byte slice so we can mutate it in place without
|
|
// further reallocation
|
|
result := []byte(strconv.FormatFloat(absValue, 'g', -1, 64))
|
|
|
|
// Simplify the exponent
|
|
// "e+05" => "e5"
|
|
// "e-05" => "e-5"
|
|
if e := bytes.LastIndexByte(result, 'e'); e != -1 {
|
|
from := e + 1
|
|
to := from
|
|
|
|
switch result[from] {
|
|
case '+':
|
|
// Strip off the leading "+"
|
|
from++
|
|
|
|
case '-':
|
|
// Skip past the leading "-"
|
|
to++
|
|
from++
|
|
}
|
|
|
|
// Strip off leading zeros
|
|
for from < len(result) && result[from] == '0' {
|
|
from++
|
|
}
|
|
|
|
result = append(result[:to], result[from:]...)
|
|
}
|
|
|
|
dot := bytes.IndexByte(result, '.')
|
|
|
|
if dot == 1 && result[0] == '0' {
|
|
// Simplify numbers starting with "0."
|
|
afterDot := 2
|
|
|
|
// Strip off the leading zero when minifying
|
|
// "0.5" => ".5"
|
|
if p.options.MinifyWhitespace {
|
|
result = result[1:]
|
|
afterDot--
|
|
}
|
|
|
|
// Try using an exponent
|
|
// "0.001" => "1e-3"
|
|
if result[afterDot] == '0' {
|
|
i := afterDot + 1
|
|
for result[i] == '0' {
|
|
i++
|
|
}
|
|
remaining := result[i:]
|
|
exponent := p.smallIntToBytes(afterDot - i - len(remaining))
|
|
|
|
// Only switch if it's actually shorter
|
|
if len(result) > len(remaining)+1+len(exponent) {
|
|
result = append(append(remaining, 'e'), exponent...)
|
|
}
|
|
}
|
|
} else if dot != -1 {
|
|
// Try to get rid of a "." and maybe also an "e"
|
|
if e := bytes.LastIndexByte(result, 'e'); e != -1 {
|
|
integer := result[:dot]
|
|
fraction := result[dot+1 : e]
|
|
exponent := parseSmallInt(result[e+1:]) - len(fraction)
|
|
|
|
// Handle small exponents by appending zeros instead
|
|
if exponent >= 0 && exponent <= 2 {
|
|
// "1.2e1" => "12"
|
|
// "1.2e2" => "120"
|
|
// "1.2e3" => "1200"
|
|
if len(result) >= len(integer)+len(fraction)+exponent {
|
|
result = append(integer, fraction...)
|
|
for i := 0; i < exponent; i++ {
|
|
result = append(result, '0')
|
|
}
|
|
}
|
|
} else {
|
|
// "1.2e4" => "12e3"
|
|
exponent := p.smallIntToBytes(exponent)
|
|
if len(result) >= len(integer)+len(fraction)+1+len(exponent) {
|
|
result = append(append(append(integer, fraction...), 'e'), exponent...)
|
|
}
|
|
}
|
|
}
|
|
} else if result[len(result)-1] == '0' {
|
|
// Simplify numbers ending with "0" by trying to use an exponent
|
|
// "1000" => "1e3"
|
|
i := len(result) - 1
|
|
for i > 0 && result[i-1] == '0' {
|
|
i--
|
|
}
|
|
remaining := result[:i]
|
|
exponent := p.smallIntToBytes(len(result) - i)
|
|
|
|
// Only switch if it's actually shorter
|
|
if len(result) > len(remaining)+1+len(exponent) {
|
|
result = append(append(remaining, 'e'), exponent...)
|
|
}
|
|
}
|
|
|
|
// Numbers in this range can potentially be printed with one fewer byte as
|
|
// hex. This compares against 0xFFFF_FFFF_FFFF_F800 instead of comparing
|
|
// against 0xFFFF_FFFF_FFFF_FFFF because 0xFFFF_FFFF_FFFF_FFFF when converted
|
|
// to float64 rounds up to 0x1_0000_0000_0000_0180, which can no longer fit
|
|
// into uint64. In Go, the result of converting float64 to uint64 outside of
|
|
// the uint64 range is implementation-dependent and is different on amd64 vs.
|
|
// arm64. The float64 value 0xFFFF_FFFF_FFFF_F800 is the biggest value that
|
|
// is below the float64 value 0x1_0000_0000_0000_0180, so we use that instead.
|
|
if p.options.MinifyWhitespace && absValue >= 1_000_000_000_000 && absValue <= 0xFFFF_FFFF_FFFF_F800 {
|
|
if asInt := uint64(absValue); absValue == float64(asInt) {
|
|
if hex := strconv.FormatUint(asInt, 16); 2+len(hex) < len(result) {
|
|
result = append(append(result[:0], '0', 'x'), hex...)
|
|
}
|
|
}
|
|
}
|
|
|
|
p.printBytes(result)
|
|
|
|
// We'll need a space before "." if it could be parsed as a decimal point
|
|
if !bytes.ContainsAny(result, ".ex") {
|
|
p.needSpaceBeforeDot = len(p.js)
|
|
}
|
|
}
|
|
|
|
func (p *printer) printDeclStmt(isExport bool, keyword string, decls []js_ast.Decl) {
|
|
p.printIndent()
|
|
p.printSpaceBeforeIdentifier()
|
|
if isExport {
|
|
p.print("export ")
|
|
}
|
|
p.printDecls(keyword, decls, 0)
|
|
p.printSemicolonAfterStatement()
|
|
}
|
|
|
|
func (p *printer) printForLoopInit(init js_ast.Stmt, flags printExprFlags) {
|
|
switch s := init.Data.(type) {
|
|
case *js_ast.SExpr:
|
|
p.printExpr(s.Value, js_ast.LLowest, flags|exprResultIsUnused)
|
|
case *js_ast.SLocal:
|
|
switch s.Kind {
|
|
case js_ast.LocalAwaitUsing:
|
|
p.printDecls("await using", s.Decls, flags)
|
|
case js_ast.LocalConst:
|
|
p.printDecls("const", s.Decls, flags)
|
|
case js_ast.LocalLet:
|
|
p.printDecls("let", s.Decls, flags)
|
|
case js_ast.LocalUsing:
|
|
p.printDecls("using", s.Decls, flags)
|
|
case js_ast.LocalVar:
|
|
p.printDecls("var", s.Decls, flags)
|
|
}
|
|
default:
|
|
panic("Internal error")
|
|
}
|
|
}
|
|
|
|
func (p *printer) printDecls(keyword string, decls []js_ast.Decl, flags printExprFlags) {
|
|
p.print(keyword)
|
|
p.printSpace()
|
|
|
|
for i, decl := range decls {
|
|
if i != 0 {
|
|
p.print(",")
|
|
if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() {
|
|
p.printSpace()
|
|
}
|
|
}
|
|
p.printBinding(decl.Binding)
|
|
|
|
if decl.ValueOrNil.Data != nil {
|
|
p.printSpace()
|
|
p.print("=")
|
|
p.printSpace()
|
|
p.printExprWithoutLeadingNewline(decl.ValueOrNil, js_ast.LComma, flags)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *printer) printBody(body js_ast.Stmt, isSingleLine bool) {
|
|
if block, ok := body.Data.(*js_ast.SBlock); ok {
|
|
p.printSpace()
|
|
p.printBlock(body.Loc, *block)
|
|
p.printNewline()
|
|
} else if isSingleLine {
|
|
p.printNextIndentAsSpace = true
|
|
p.printStmt(body, 0)
|
|
} else {
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printStmt(body, 0)
|
|
p.options.Indent--
|
|
}
|
|
}
|
|
|
|
func (p *printer) printBlock(loc logger.Loc, block js_ast.SBlock) {
|
|
p.addSourceMapping(loc)
|
|
p.print("{")
|
|
p.printNewline()
|
|
|
|
p.options.Indent++
|
|
for _, stmt := range block.Stmts {
|
|
p.printSemicolonIfNeeded()
|
|
p.printStmt(stmt, canOmitStatement)
|
|
}
|
|
p.options.Indent--
|
|
p.needsSemicolon = false
|
|
|
|
p.printIndent()
|
|
if block.CloseBraceLoc.Start > loc.Start {
|
|
p.addSourceMapping(block.CloseBraceLoc)
|
|
}
|
|
p.print("}")
|
|
}
|
|
|
|
func wrapToAvoidAmbiguousElse(s js_ast.S) bool {
|
|
for {
|
|
switch current := s.(type) {
|
|
case *js_ast.SIf:
|
|
if current.NoOrNil.Data == nil {
|
|
return true
|
|
}
|
|
s = current.NoOrNil.Data
|
|
|
|
case *js_ast.SFor:
|
|
s = current.Body.Data
|
|
|
|
case *js_ast.SForIn:
|
|
s = current.Body.Data
|
|
|
|
case *js_ast.SForOf:
|
|
s = current.Body.Data
|
|
|
|
case *js_ast.SWhile:
|
|
s = current.Body.Data
|
|
|
|
case *js_ast.SWith:
|
|
s = current.Body.Data
|
|
|
|
case *js_ast.SLabel:
|
|
s = current.Stmt.Data
|
|
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *printer) printIf(s *js_ast.SIf) {
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("if")
|
|
p.printSpace()
|
|
p.print("(")
|
|
if p.willPrintExprCommentsAtLoc(s.Test.Loc) {
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
p.printExpr(s.Test, js_ast.LLowest, 0)
|
|
p.printNewline()
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
} else {
|
|
p.printExpr(s.Test, js_ast.LLowest, 0)
|
|
}
|
|
p.print(")")
|
|
|
|
// Simplify the else branch, which may disappear entirely
|
|
no := s.NoOrNil
|
|
if expr, ok := no.Data.(*js_ast.SExpr); ok {
|
|
if value := p.simplifyUnusedExpr(expr.Value); value.Data == nil {
|
|
no.Data = nil
|
|
} else if value.Data != expr.Value.Data {
|
|
no.Data = &js_ast.SExpr{Value: value}
|
|
}
|
|
}
|
|
|
|
if yes, ok := s.Yes.Data.(*js_ast.SBlock); ok {
|
|
p.printSpace()
|
|
p.printBlock(s.Yes.Loc, *yes)
|
|
|
|
if no.Data != nil {
|
|
p.printSpace()
|
|
} else {
|
|
p.printNewline()
|
|
}
|
|
} else if wrapToAvoidAmbiguousElse(s.Yes.Data) {
|
|
p.printSpace()
|
|
p.print("{")
|
|
p.printNewline()
|
|
|
|
p.options.Indent++
|
|
p.printStmt(s.Yes, canOmitStatement)
|
|
p.options.Indent--
|
|
p.needsSemicolon = false
|
|
|
|
p.printIndent()
|
|
p.print("}")
|
|
|
|
if no.Data != nil {
|
|
p.printSpace()
|
|
} else {
|
|
p.printNewline()
|
|
}
|
|
} else {
|
|
p.printBody(s.Yes, s.IsSingleLineYes)
|
|
|
|
if no.Data != nil {
|
|
p.printIndent()
|
|
}
|
|
}
|
|
|
|
if no.Data != nil {
|
|
p.printSemicolonIfNeeded()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("else")
|
|
|
|
if block, ok := no.Data.(*js_ast.SBlock); ok {
|
|
p.printSpace()
|
|
p.printBlock(no.Loc, *block)
|
|
p.printNewline()
|
|
} else if ifStmt, ok := no.Data.(*js_ast.SIf); ok {
|
|
p.printIf(ifStmt)
|
|
} else {
|
|
p.printBody(no, s.IsSingleLineNo)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *printer) printIndentedComment(text string) {
|
|
// Avoid generating a comment containing the character sequence "</script"
|
|
if !p.options.UnsupportedFeatures.Has(compat.InlineScript) {
|
|
text = helpers.EscapeClosingTag(text, "/script")
|
|
}
|
|
|
|
if strings.HasPrefix(text, "/*") {
|
|
// Re-indent multi-line comments
|
|
for {
|
|
newline := strings.IndexByte(text, '\n')
|
|
if newline == -1 {
|
|
break
|
|
}
|
|
p.print(text[:newline+1])
|
|
p.printIndent()
|
|
text = text[newline+1:]
|
|
}
|
|
p.print(text)
|
|
p.printNewline()
|
|
} else {
|
|
// Print a mandatory newline after single-line comments
|
|
p.print(text)
|
|
p.print("\n")
|
|
}
|
|
}
|
|
|
|
func (p *printer) printPath(importRecordIndex uint32, importKind ast.ImportKind) {
|
|
record := p.importRecords[importRecordIndex]
|
|
p.addSourceMapping(record.Range.Loc)
|
|
p.printQuotedUTF8(record.Path.Text, printQuotedNoWrap)
|
|
|
|
if p.options.NeedsMetafile {
|
|
external := ""
|
|
if (record.Flags & ast.ShouldNotBeExternalInMetafile) == 0 {
|
|
external = p.options.MetafileFormat.MaybeRemoveWhitespace(",\n \"external\": true")
|
|
}
|
|
p.jsonMetadataImports = append(p.jsonMetadataImports, fmt.Sprintf(
|
|
p.options.MetafileFormat.MaybeRemoveWhitespace("\n {\n \"path\": %s,\n \"kind\": %s%s\n }"),
|
|
helpers.QuoteForJSON(record.Path.Text, p.options.ASCIIOnly),
|
|
helpers.QuoteForJSON(importKind.StringForMetafile(), p.options.ASCIIOnly),
|
|
external))
|
|
}
|
|
|
|
if record.AssertOrWith != nil && importKind == ast.ImportStmt {
|
|
feature := compat.ImportAttributes
|
|
if record.AssertOrWith.Keyword == ast.AssertKeyword {
|
|
feature = compat.ImportAssertions
|
|
}
|
|
|
|
// Omit import assertions/attributes on this import statement if they would cause a syntax error
|
|
if p.options.UnsupportedFeatures.Has(feature) {
|
|
return
|
|
}
|
|
|
|
p.printSpace()
|
|
p.addSourceMapping(record.AssertOrWith.KeywordLoc)
|
|
p.print(record.AssertOrWith.Keyword.String())
|
|
p.printSpace()
|
|
p.printImportAssertOrWithClause(*record.AssertOrWith)
|
|
}
|
|
}
|
|
|
|
func (p *printer) printImportCallAssertOrWith(assertOrWith *ast.ImportAssertOrWith, outerIsMultiLine bool) {
|
|
// Omit import assertions/attributes if we know the "import()" syntax doesn't
|
|
// support a second argument (i.e. both import assertions and import
|
|
// attributes aren't supported) and doing so would cause a syntax error
|
|
if assertOrWith == nil || (p.options.UnsupportedFeatures.Has(compat.ImportAssertions) && p.options.UnsupportedFeatures.Has(compat.ImportAttributes)) {
|
|
return
|
|
}
|
|
|
|
isMultiLine := p.willPrintExprCommentsAtLoc(assertOrWith.KeywordLoc) ||
|
|
p.willPrintExprCommentsAtLoc(assertOrWith.InnerOpenBraceLoc) ||
|
|
p.willPrintExprCommentsAtLoc(assertOrWith.OuterCloseBraceLoc)
|
|
|
|
p.print(",")
|
|
if outerIsMultiLine {
|
|
p.printNewline()
|
|
p.printIndent()
|
|
} else {
|
|
p.printSpace()
|
|
}
|
|
p.printExprCommentsAtLoc(assertOrWith.OuterOpenBraceLoc)
|
|
p.addSourceMapping(assertOrWith.OuterOpenBraceLoc)
|
|
p.print("{")
|
|
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
} else {
|
|
p.printSpace()
|
|
}
|
|
|
|
p.printExprCommentsAtLoc(assertOrWith.KeywordLoc)
|
|
p.addSourceMapping(assertOrWith.KeywordLoc)
|
|
p.print(assertOrWith.Keyword.String())
|
|
p.print(":")
|
|
|
|
if p.willPrintExprCommentsAtLoc(assertOrWith.InnerOpenBraceLoc) {
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
p.printExprCommentsAtLoc(assertOrWith.InnerOpenBraceLoc)
|
|
p.printImportAssertOrWithClause(*assertOrWith)
|
|
p.options.Indent--
|
|
} else {
|
|
p.printSpace()
|
|
p.printImportAssertOrWithClause(*assertOrWith)
|
|
}
|
|
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.printExprCommentsAfterCloseTokenAtLoc(assertOrWith.OuterCloseBraceLoc)
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
} else {
|
|
p.printSpace()
|
|
}
|
|
|
|
p.addSourceMapping(assertOrWith.OuterCloseBraceLoc)
|
|
p.print("}")
|
|
}
|
|
|
|
func (p *printer) printImportAssertOrWithClause(assertOrWith ast.ImportAssertOrWith) {
|
|
isMultiLine := p.willPrintExprCommentsAtLoc(assertOrWith.InnerCloseBraceLoc)
|
|
if !isMultiLine {
|
|
for _, entry := range assertOrWith.Entries {
|
|
if p.willPrintExprCommentsAtLoc(entry.KeyLoc) || p.willPrintExprCommentsAtLoc(entry.ValueLoc) {
|
|
isMultiLine = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
p.addSourceMapping(assertOrWith.InnerOpenBraceLoc)
|
|
p.print("{")
|
|
if isMultiLine {
|
|
p.options.Indent++
|
|
}
|
|
|
|
for i, entry := range assertOrWith.Entries {
|
|
if i > 0 {
|
|
p.print(",")
|
|
}
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.printIndent()
|
|
} else {
|
|
p.printSpace()
|
|
}
|
|
|
|
p.printExprCommentsAtLoc(entry.KeyLoc)
|
|
p.addSourceMapping(entry.KeyLoc)
|
|
if !entry.PreferQuotedKey && p.canPrintIdentifierUTF16(entry.Key) {
|
|
p.printSpaceBeforeIdentifier()
|
|
p.printIdentifierUTF16(entry.Key)
|
|
} else {
|
|
p.printQuotedUTF16(entry.Key, 0)
|
|
}
|
|
|
|
p.print(":")
|
|
|
|
if p.willPrintExprCommentsAtLoc(entry.ValueLoc) {
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
p.printExprCommentsAtLoc(entry.ValueLoc)
|
|
p.addSourceMapping(entry.ValueLoc)
|
|
p.printQuotedUTF16(entry.Value, 0)
|
|
p.options.Indent--
|
|
} else {
|
|
p.printSpace()
|
|
p.addSourceMapping(entry.ValueLoc)
|
|
p.printQuotedUTF16(entry.Value, 0)
|
|
}
|
|
}
|
|
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.printExprCommentsAfterCloseTokenAtLoc(assertOrWith.InnerCloseBraceLoc)
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
} else if len(assertOrWith.Entries) > 0 {
|
|
p.printSpace()
|
|
}
|
|
|
|
p.addSourceMapping(assertOrWith.InnerCloseBraceLoc)
|
|
p.print("}")
|
|
}
|
|
|
|
type printStmtFlags uint8
|
|
|
|
const (
|
|
canOmitStatement printStmtFlags = 1 << iota
|
|
)
|
|
|
|
func (p *printer) printStmt(stmt js_ast.Stmt, flags printStmtFlags) {
|
|
if p.options.LineLimit > 0 {
|
|
p.printNewlinePastLineLimit()
|
|
}
|
|
|
|
switch s := stmt.Data.(type) {
|
|
case *js_ast.SComment:
|
|
text := s.Text
|
|
|
|
if s.IsLegalComment {
|
|
switch p.options.LegalComments {
|
|
case config.LegalCommentsNone:
|
|
return
|
|
|
|
case config.LegalCommentsEndOfFile,
|
|
config.LegalCommentsLinkedWithComment,
|
|
config.LegalCommentsExternalWithoutComment:
|
|
|
|
// Don't record the same legal comment more than once per file
|
|
if p.hasLegalComment == nil {
|
|
p.hasLegalComment = make(map[string]struct{})
|
|
} else if _, ok := p.hasLegalComment[text]; ok {
|
|
return
|
|
}
|
|
p.hasLegalComment[text] = struct{}{}
|
|
p.extractedLegalComments = append(p.extractedLegalComments, text)
|
|
return
|
|
}
|
|
}
|
|
|
|
p.printIndent()
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndentedComment(text)
|
|
|
|
case *js_ast.SFunction:
|
|
if !p.options.MinifyWhitespace && s.Fn.HasNoSideEffectsComment {
|
|
p.printIndent()
|
|
p.print("// @__NO_SIDE_EFFECTS__\n")
|
|
}
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.printSpaceBeforeIdentifier()
|
|
if s.IsExport {
|
|
p.print("export ")
|
|
}
|
|
if s.Fn.IsAsync {
|
|
p.print("async ")
|
|
}
|
|
p.print("function")
|
|
if s.Fn.IsGenerator {
|
|
p.print("*")
|
|
p.printSpace()
|
|
}
|
|
p.printSpaceBeforeIdentifier()
|
|
name := p.renamer.NameForSymbol(s.Fn.Name.Ref)
|
|
p.addSourceMappingForName(s.Fn.Name.Loc, name, s.Fn.Name.Ref)
|
|
p.printIdentifier(name)
|
|
p.printFn(s.Fn)
|
|
p.printNewline()
|
|
|
|
case *js_ast.SClass:
|
|
omitIndent := p.printDecorators(s.Class.Decorators, printNewlineAfterDecorator)
|
|
if !omitIndent {
|
|
p.printIndent()
|
|
}
|
|
p.printSpaceBeforeIdentifier()
|
|
p.addSourceMapping(stmt.Loc)
|
|
if s.IsExport {
|
|
p.print("export ")
|
|
}
|
|
p.print("class ")
|
|
name := p.renamer.NameForSymbol(s.Class.Name.Ref)
|
|
p.addSourceMappingForName(s.Class.Name.Loc, name, s.Class.Name.Ref)
|
|
p.printIdentifier(name)
|
|
p.printClass(s.Class)
|
|
p.printNewline()
|
|
|
|
case *js_ast.SEmpty:
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.print(";")
|
|
p.printNewline()
|
|
|
|
case *js_ast.SExportDefault:
|
|
if !p.options.MinifyWhitespace {
|
|
if s2, ok := s.Value.Data.(*js_ast.SFunction); ok && s2.Fn.HasNoSideEffectsComment {
|
|
p.printIndent()
|
|
p.print("// @__NO_SIDE_EFFECTS__\n")
|
|
}
|
|
}
|
|
omitIndent := false
|
|
if s2, ok := s.Value.Data.(*js_ast.SClass); ok {
|
|
omitIndent = p.printDecorators(s2.Class.Decorators, printNewlineAfterDecorator)
|
|
}
|
|
p.addSourceMapping(stmt.Loc)
|
|
if !omitIndent {
|
|
p.printIndent()
|
|
}
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("export default")
|
|
p.printSpace()
|
|
|
|
switch s2 := s.Value.Data.(type) {
|
|
case *js_ast.SExpr:
|
|
// Functions and classes must be wrapped to avoid confusion with their statement forms
|
|
p.exportDefaultStart = len(p.js)
|
|
|
|
p.printExprWithoutLeadingNewline(s2.Value, js_ast.LComma, 0)
|
|
p.printSemicolonAfterStatement()
|
|
return
|
|
|
|
case *js_ast.SFunction:
|
|
p.printSpaceBeforeIdentifier()
|
|
if s2.Fn.IsAsync {
|
|
p.print("async ")
|
|
}
|
|
p.print("function")
|
|
if s2.Fn.IsGenerator {
|
|
p.print("*")
|
|
p.printSpace()
|
|
}
|
|
if s2.Fn.Name != nil {
|
|
p.printSpaceBeforeIdentifier()
|
|
name := p.renamer.NameForSymbol(s2.Fn.Name.Ref)
|
|
p.addSourceMappingForName(s2.Fn.Name.Loc, name, s2.Fn.Name.Ref)
|
|
p.printIdentifier(name)
|
|
}
|
|
p.printFn(s2.Fn)
|
|
p.printNewline()
|
|
|
|
case *js_ast.SClass:
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("class")
|
|
if s2.Class.Name != nil {
|
|
p.print(" ")
|
|
name := p.renamer.NameForSymbol(s2.Class.Name.Ref)
|
|
p.addSourceMappingForName(s2.Class.Name.Loc, name, s2.Class.Name.Ref)
|
|
p.printIdentifier(name)
|
|
}
|
|
p.printClass(s2.Class)
|
|
p.printNewline()
|
|
|
|
default:
|
|
panic("Internal error")
|
|
}
|
|
|
|
case *js_ast.SExportStar:
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("export")
|
|
p.printSpace()
|
|
p.print("*")
|
|
p.printSpace()
|
|
if s.Alias != nil {
|
|
p.print("as")
|
|
p.printSpace()
|
|
p.printClauseAlias(s.Alias.Loc, s.Alias.OriginalName)
|
|
p.printSpace()
|
|
p.printSpaceBeforeIdentifier()
|
|
}
|
|
p.print("from")
|
|
p.printSpace()
|
|
p.printPath(s.ImportRecordIndex, ast.ImportStmt)
|
|
p.printSemicolonAfterStatement()
|
|
|
|
case *js_ast.SExportClause:
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("export")
|
|
p.printSpace()
|
|
p.print("{")
|
|
|
|
if !s.IsSingleLine {
|
|
p.options.Indent++
|
|
}
|
|
|
|
for i, item := range s.Items {
|
|
if i != 0 {
|
|
p.print(",")
|
|
}
|
|
|
|
if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() {
|
|
if s.IsSingleLine {
|
|
p.printSpace()
|
|
} else {
|
|
p.printNewline()
|
|
p.printIndent()
|
|
}
|
|
}
|
|
|
|
name := p.renamer.NameForSymbol(item.Name.Ref)
|
|
p.addSourceMappingForName(item.Name.Loc, name, item.Name.Ref)
|
|
p.printIdentifier(name)
|
|
if name != item.Alias {
|
|
p.print(" as")
|
|
p.printSpace()
|
|
p.printClauseAlias(item.AliasLoc, item.Alias)
|
|
}
|
|
}
|
|
|
|
if !s.IsSingleLine {
|
|
p.options.Indent--
|
|
p.printNewline()
|
|
p.printIndent()
|
|
} else if len(s.Items) > 0 {
|
|
p.printSpace()
|
|
}
|
|
|
|
p.print("}")
|
|
p.printSemicolonAfterStatement()
|
|
|
|
case *js_ast.SExportFrom:
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("export")
|
|
p.printSpace()
|
|
p.print("{")
|
|
|
|
if !s.IsSingleLine {
|
|
p.options.Indent++
|
|
}
|
|
|
|
for i, item := range s.Items {
|
|
if i != 0 {
|
|
p.print(",")
|
|
}
|
|
|
|
if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() {
|
|
if s.IsSingleLine {
|
|
p.printSpace()
|
|
} else {
|
|
p.printNewline()
|
|
p.printIndent()
|
|
}
|
|
}
|
|
|
|
p.printClauseAlias(item.Name.Loc, item.OriginalName)
|
|
if item.OriginalName != item.Alias {
|
|
p.printSpace()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("as")
|
|
p.printSpace()
|
|
p.printClauseAlias(item.AliasLoc, item.Alias)
|
|
}
|
|
}
|
|
|
|
if !s.IsSingleLine {
|
|
p.options.Indent--
|
|
p.printNewline()
|
|
p.printIndent()
|
|
} else if len(s.Items) > 0 {
|
|
p.printSpace()
|
|
}
|
|
|
|
p.print("}")
|
|
p.printSpace()
|
|
p.print("from")
|
|
p.printSpace()
|
|
p.printPath(s.ImportRecordIndex, ast.ImportStmt)
|
|
p.printSemicolonAfterStatement()
|
|
|
|
case *js_ast.SLocal:
|
|
p.addSourceMapping(stmt.Loc)
|
|
switch s.Kind {
|
|
case js_ast.LocalAwaitUsing:
|
|
p.printDeclStmt(s.IsExport, "await using", s.Decls)
|
|
case js_ast.LocalConst:
|
|
p.printDeclStmt(s.IsExport, "const", s.Decls)
|
|
case js_ast.LocalLet:
|
|
p.printDeclStmt(s.IsExport, "let", s.Decls)
|
|
case js_ast.LocalUsing:
|
|
p.printDeclStmt(s.IsExport, "using", s.Decls)
|
|
case js_ast.LocalVar:
|
|
p.printDeclStmt(s.IsExport, "var", s.Decls)
|
|
}
|
|
|
|
case *js_ast.SIf:
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.printIf(s)
|
|
|
|
case *js_ast.SDoWhile:
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("do")
|
|
if block, ok := s.Body.Data.(*js_ast.SBlock); ok {
|
|
p.printSpace()
|
|
p.printBlock(s.Body.Loc, *block)
|
|
p.printSpace()
|
|
} else {
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printStmt(s.Body, 0)
|
|
p.printSemicolonIfNeeded()
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
}
|
|
p.print("while")
|
|
p.printSpace()
|
|
p.print("(")
|
|
if p.willPrintExprCommentsAtLoc(s.Test.Loc) {
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
p.printExpr(s.Test, js_ast.LLowest, 0)
|
|
p.printNewline()
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
} else {
|
|
p.printExpr(s.Test, js_ast.LLowest, 0)
|
|
}
|
|
p.print(")")
|
|
p.printSemicolonAfterStatement()
|
|
|
|
case *js_ast.SForIn:
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("for")
|
|
p.printSpace()
|
|
p.print("(")
|
|
hasInitComment := p.willPrintExprCommentsAtLoc(s.Init.Loc)
|
|
hasValueComment := p.willPrintExprCommentsAtLoc(s.Value.Loc)
|
|
if hasInitComment || hasValueComment {
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
}
|
|
p.printForLoopInit(s.Init, forbidIn)
|
|
p.printSpace()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("in")
|
|
if hasValueComment {
|
|
p.printNewline()
|
|
p.printIndent()
|
|
} else {
|
|
p.printSpace()
|
|
}
|
|
p.printExpr(s.Value, js_ast.LLowest, 0)
|
|
if hasInitComment || hasValueComment {
|
|
p.printNewline()
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
}
|
|
p.print(")")
|
|
p.printBody(s.Body, s.IsSingleLineBody)
|
|
|
|
case *js_ast.SForOf:
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("for")
|
|
if s.Await.Len > 0 {
|
|
p.print(" await")
|
|
}
|
|
p.printSpace()
|
|
p.print("(")
|
|
hasInitComment := p.willPrintExprCommentsAtLoc(s.Init.Loc)
|
|
hasValueComment := p.willPrintExprCommentsAtLoc(s.Value.Loc)
|
|
flags := forbidIn | isFollowedByOf
|
|
if s.Await.Len > 0 {
|
|
flags |= isInsideForAwait
|
|
}
|
|
if hasInitComment || hasValueComment {
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
}
|
|
p.forOfInitStart = len(p.js)
|
|
p.printForLoopInit(s.Init, flags)
|
|
p.printSpace()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("of")
|
|
if hasValueComment {
|
|
p.printNewline()
|
|
p.printIndent()
|
|
} else {
|
|
p.printSpace()
|
|
}
|
|
p.printExpr(s.Value, js_ast.LComma, 0)
|
|
if hasInitComment || hasValueComment {
|
|
p.printNewline()
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
}
|
|
p.print(")")
|
|
p.printBody(s.Body, s.IsSingleLineBody)
|
|
|
|
case *js_ast.SWhile:
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("while")
|
|
p.printSpace()
|
|
p.print("(")
|
|
if p.willPrintExprCommentsAtLoc(s.Test.Loc) {
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
p.printExpr(s.Test, js_ast.LLowest, 0)
|
|
p.printNewline()
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
} else {
|
|
p.printExpr(s.Test, js_ast.LLowest, 0)
|
|
}
|
|
p.print(")")
|
|
p.printBody(s.Body, s.IsSingleLineBody)
|
|
|
|
case *js_ast.SWith:
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("with")
|
|
p.printSpace()
|
|
p.print("(")
|
|
if p.willPrintExprCommentsAtLoc(s.Value.Loc) {
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
p.printExpr(s.Value, js_ast.LLowest, 0)
|
|
p.printNewline()
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
} else {
|
|
p.printExpr(s.Value, js_ast.LLowest, 0)
|
|
}
|
|
p.print(")")
|
|
p.withNesting++
|
|
p.printBody(s.Body, s.IsSingleLineBody)
|
|
p.withNesting--
|
|
|
|
case *js_ast.SLabel:
|
|
// Avoid printing a source mapping that masks the one from the label
|
|
if !p.options.MinifyWhitespace && (p.options.Indent > 0 || p.printNextIndentAsSpace) {
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
}
|
|
|
|
p.printSpaceBeforeIdentifier()
|
|
name := p.renamer.NameForSymbol(s.Name.Ref)
|
|
p.addSourceMappingForName(s.Name.Loc, name, s.Name.Ref)
|
|
p.printIdentifier(name)
|
|
p.print(":")
|
|
p.printBody(s.Stmt, s.IsSingleLineStmt)
|
|
|
|
case *js_ast.STry:
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("try")
|
|
p.printSpace()
|
|
p.printBlock(s.BlockLoc, s.Block)
|
|
|
|
if s.Catch != nil {
|
|
p.printSpace()
|
|
p.print("catch")
|
|
if s.Catch.BindingOrNil.Data != nil {
|
|
p.printSpace()
|
|
p.print("(")
|
|
p.printBinding(s.Catch.BindingOrNil)
|
|
p.print(")")
|
|
}
|
|
p.printSpace()
|
|
p.printBlock(s.Catch.BlockLoc, s.Catch.Block)
|
|
}
|
|
|
|
if s.Finally != nil {
|
|
p.printSpace()
|
|
p.print("finally")
|
|
p.printSpace()
|
|
p.printBlock(s.Finally.Loc, s.Finally.Block)
|
|
}
|
|
|
|
p.printNewline()
|
|
|
|
case *js_ast.SFor:
|
|
init := s.InitOrNil
|
|
update := s.UpdateOrNil
|
|
|
|
// Omit calls to empty functions from the output completely
|
|
if p.options.MinifySyntax {
|
|
if expr, ok := init.Data.(*js_ast.SExpr); ok {
|
|
if value := p.simplifyUnusedExpr(expr.Value); value.Data == nil {
|
|
init.Data = nil
|
|
} else if value.Data != expr.Value.Data {
|
|
init.Data = &js_ast.SExpr{Value: value}
|
|
}
|
|
}
|
|
if update.Data != nil {
|
|
update = p.simplifyUnusedExpr(update)
|
|
}
|
|
}
|
|
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("for")
|
|
p.printSpace()
|
|
p.print("(")
|
|
isMultiLine :=
|
|
(init.Data != nil && p.willPrintExprCommentsAtLoc(init.Loc)) ||
|
|
(s.TestOrNil.Data != nil && p.willPrintExprCommentsAtLoc(s.TestOrNil.Loc)) ||
|
|
(update.Data != nil && p.willPrintExprCommentsAtLoc(update.Loc))
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
}
|
|
if init.Data != nil {
|
|
p.printForLoopInit(init, forbidIn)
|
|
}
|
|
p.print(";")
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.printIndent()
|
|
} else {
|
|
p.printSpace()
|
|
}
|
|
if s.TestOrNil.Data != nil {
|
|
p.printExpr(s.TestOrNil, js_ast.LLowest, 0)
|
|
}
|
|
p.print(";")
|
|
if !isMultiLine {
|
|
p.printSpace()
|
|
} else if update.Data != nil {
|
|
p.printNewline()
|
|
p.printIndent()
|
|
}
|
|
if update.Data != nil {
|
|
p.printExpr(update, js_ast.LLowest, exprResultIsUnused)
|
|
}
|
|
if isMultiLine {
|
|
p.printNewline()
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
}
|
|
p.print(")")
|
|
p.printBody(s.Body, s.IsSingleLineBody)
|
|
|
|
case *js_ast.SSwitch:
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("switch")
|
|
p.printSpace()
|
|
p.print("(")
|
|
if p.willPrintExprCommentsAtLoc(s.Test.Loc) {
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
p.printIndent()
|
|
p.printExpr(s.Test, js_ast.LLowest, 0)
|
|
p.printNewline()
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
} else {
|
|
p.printExpr(s.Test, js_ast.LLowest, 0)
|
|
}
|
|
p.print(")")
|
|
p.printSpace()
|
|
p.addSourceMapping(s.BodyLoc)
|
|
p.print("{")
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
|
|
for _, c := range s.Cases {
|
|
p.printSemicolonIfNeeded()
|
|
p.printIndent()
|
|
p.printExprCommentsAtLoc(c.Loc)
|
|
p.addSourceMapping(c.Loc)
|
|
|
|
if c.ValueOrNil.Data != nil {
|
|
p.print("case")
|
|
p.printSpace()
|
|
p.printExpr(c.ValueOrNil, js_ast.LLogicalAnd, 0)
|
|
} else {
|
|
p.print("default")
|
|
}
|
|
p.print(":")
|
|
|
|
if len(c.Body) == 1 {
|
|
if block, ok := c.Body[0].Data.(*js_ast.SBlock); ok {
|
|
p.printSpace()
|
|
p.printBlock(c.Body[0].Loc, *block)
|
|
p.printNewline()
|
|
continue
|
|
}
|
|
}
|
|
|
|
p.printNewline()
|
|
p.options.Indent++
|
|
for _, stmt := range c.Body {
|
|
p.printSemicolonIfNeeded()
|
|
p.printStmt(stmt, canOmitStatement)
|
|
}
|
|
p.options.Indent--
|
|
}
|
|
|
|
p.options.Indent--
|
|
p.printIndent()
|
|
p.addSourceMapping(s.CloseBraceLoc)
|
|
p.print("}")
|
|
p.printNewline()
|
|
p.needsSemicolon = false
|
|
|
|
case *js_ast.SImport:
|
|
itemCount := 0
|
|
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.printSpaceBeforeIdentifier()
|
|
switch p.importRecords[s.ImportRecordIndex].Phase {
|
|
case ast.DeferPhase:
|
|
p.print("import defer")
|
|
case ast.SourcePhase:
|
|
p.print("import source")
|
|
default:
|
|
p.print("import")
|
|
}
|
|
p.printSpace()
|
|
|
|
if s.DefaultName != nil {
|
|
p.printSpaceBeforeIdentifier()
|
|
name := p.renamer.NameForSymbol(s.DefaultName.Ref)
|
|
p.addSourceMappingForName(s.DefaultName.Loc, name, s.DefaultName.Ref)
|
|
p.printIdentifier(name)
|
|
itemCount++
|
|
}
|
|
|
|
if s.Items != nil {
|
|
if itemCount > 0 {
|
|
p.print(",")
|
|
p.printSpace()
|
|
}
|
|
|
|
p.print("{")
|
|
if !s.IsSingleLine {
|
|
p.options.Indent++
|
|
}
|
|
|
|
for i, item := range *s.Items {
|
|
if i != 0 {
|
|
p.print(",")
|
|
}
|
|
|
|
if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() {
|
|
if s.IsSingleLine {
|
|
p.printSpace()
|
|
} else {
|
|
p.printNewline()
|
|
p.printIndent()
|
|
}
|
|
}
|
|
|
|
p.printClauseAlias(item.AliasLoc, item.Alias)
|
|
|
|
name := p.renamer.NameForSymbol(item.Name.Ref)
|
|
if name != item.Alias {
|
|
p.printSpace()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("as ")
|
|
p.addSourceMappingForName(item.Name.Loc, name, item.Name.Ref)
|
|
p.printIdentifier(name)
|
|
}
|
|
}
|
|
|
|
if !s.IsSingleLine {
|
|
p.options.Indent--
|
|
p.printNewline()
|
|
p.printIndent()
|
|
} else if len(*s.Items) > 0 {
|
|
p.printSpace()
|
|
}
|
|
|
|
p.print("}")
|
|
itemCount++
|
|
}
|
|
|
|
if s.StarNameLoc != nil {
|
|
if itemCount > 0 {
|
|
p.print(",")
|
|
p.printSpace()
|
|
}
|
|
|
|
p.print("*")
|
|
p.printSpace()
|
|
p.print("as ")
|
|
name := p.renamer.NameForSymbol(s.NamespaceRef)
|
|
p.addSourceMappingForName(*s.StarNameLoc, name, s.NamespaceRef)
|
|
p.printIdentifier(name)
|
|
itemCount++
|
|
}
|
|
|
|
if itemCount > 0 {
|
|
p.printSpace()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("from")
|
|
p.printSpace()
|
|
}
|
|
|
|
p.printPath(s.ImportRecordIndex, ast.ImportStmt)
|
|
p.printSemicolonAfterStatement()
|
|
|
|
case *js_ast.SBlock:
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.printBlock(stmt.Loc, *s)
|
|
p.printNewline()
|
|
|
|
case *js_ast.SDebugger:
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("debugger")
|
|
p.printSemicolonAfterStatement()
|
|
|
|
case *js_ast.SDirective:
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.printQuotedUTF16(s.Value, 0)
|
|
p.printSemicolonAfterStatement()
|
|
|
|
case *js_ast.SBreak:
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("break")
|
|
if s.Label != nil {
|
|
p.print(" ")
|
|
name := p.renamer.NameForSymbol(s.Label.Ref)
|
|
p.addSourceMappingForName(s.Label.Loc, name, s.Label.Ref)
|
|
p.printIdentifier(name)
|
|
}
|
|
p.printSemicolonAfterStatement()
|
|
|
|
case *js_ast.SContinue:
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("continue")
|
|
if s.Label != nil {
|
|
p.print(" ")
|
|
name := p.renamer.NameForSymbol(s.Label.Ref)
|
|
p.addSourceMappingForName(s.Label.Loc, name, s.Label.Ref)
|
|
p.printIdentifier(name)
|
|
}
|
|
p.printSemicolonAfterStatement()
|
|
|
|
case *js_ast.SReturn:
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("return")
|
|
if s.ValueOrNil.Data != nil {
|
|
p.printSpace()
|
|
p.printExprWithoutLeadingNewline(s.ValueOrNil, js_ast.LLowest, 0)
|
|
}
|
|
p.printSemicolonAfterStatement()
|
|
|
|
case *js_ast.SThrow:
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.printSpaceBeforeIdentifier()
|
|
p.print("throw")
|
|
p.printSpace()
|
|
p.printExprWithoutLeadingNewline(s.Value, js_ast.LLowest, 0)
|
|
p.printSemicolonAfterStatement()
|
|
|
|
case *js_ast.SExpr:
|
|
value := s.Value
|
|
|
|
// Omit calls to empty functions from the output completely
|
|
if p.options.MinifySyntax {
|
|
value = p.simplifyUnusedExpr(value)
|
|
if value.Data == nil {
|
|
// If this statement is not in a block, then we still need to emit something
|
|
if (flags & canOmitStatement) == 0 {
|
|
// "if (x) empty();" => "if (x) ;"
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
p.print(";")
|
|
p.printNewline()
|
|
} else {
|
|
// "if (x) { empty(); }" => "if (x) {}"
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
// Avoid printing a source mapping when the expression would print one in
|
|
// the same spot. We don't want to accidentally mask the mapping it emits.
|
|
if !p.options.MinifyWhitespace && (p.options.Indent > 0 || p.printNextIndentAsSpace) {
|
|
p.addSourceMapping(stmt.Loc)
|
|
p.printIndent()
|
|
}
|
|
|
|
p.stmtStart = len(p.js)
|
|
p.printExpr(value, js_ast.LLowest, exprResultIsUnused)
|
|
p.printSemicolonAfterStatement()
|
|
|
|
default:
|
|
panic(fmt.Sprintf("Unexpected statement of type %T", stmt.Data))
|
|
}
|
|
}
|
|
|
|
type Options struct {
|
|
RequireOrImportMetaForSource func(uint32) RequireOrImportMeta
|
|
|
|
// Cross-module inlining of TypeScript enums is actually done during printing
|
|
TSEnums map[ast.Ref]map[string]js_ast.TSEnumValue
|
|
|
|
// Cross-module inlining of detected inlinable constants is also done during printing
|
|
ConstValues map[ast.Ref]js_ast.ConstValue
|
|
|
|
// Property mangling results go here
|
|
MangledProps map[ast.Ref]string
|
|
|
|
// This will be present if the input file had a source map. In that case we
|
|
// want to map all the way back to the original input file(s).
|
|
InputSourceMap *sourcemap.SourceMap
|
|
|
|
// If we're writing out a source map, this table of line start indices lets
|
|
// us do binary search on to figure out what line a given AST node came from
|
|
LineOffsetTables []sourcemap.LineOffsetTable
|
|
|
|
ToCommonJSRef ast.Ref
|
|
ToESMRef ast.Ref
|
|
RuntimeRequireRef ast.Ref
|
|
UnsupportedFeatures compat.JSFeature
|
|
Indent int
|
|
LineLimit int
|
|
OutputFormat config.Format
|
|
MinifyWhitespace bool
|
|
MinifyIdentifiers bool
|
|
MinifySyntax bool
|
|
ASCIIOnly bool
|
|
LegalComments config.LegalComments
|
|
SourceMap config.SourceMap
|
|
AddSourceMappings bool
|
|
NeedsMetafile bool
|
|
MetafileFormat config.MetafileFormat
|
|
}
|
|
|
|
type RequireOrImportMeta struct {
|
|
// CommonJS files will return the "require_*" wrapper function and an invalid
|
|
// exports object reference. Lazily-initialized ESM files will return the
|
|
// "init_*" wrapper function and the exports object for that file.
|
|
WrapperRef ast.Ref
|
|
ExportsRef ast.Ref
|
|
IsWrapperAsync bool
|
|
}
|
|
|
|
type PrintResult struct {
|
|
JS []byte
|
|
ExtractedLegalComments []string
|
|
JSONMetadataImports []string
|
|
|
|
// This source map chunk just contains the VLQ-encoded offsets for the "JS"
|
|
// field above. It's not a full source map. The bundler will be joining many
|
|
// source map chunks together to form the final source map.
|
|
SourceMapChunk sourcemap.Chunk
|
|
}
|
|
|
|
func Print(tree js_ast.AST, symbols ast.SymbolMap, r renamer.Renamer, options Options) PrintResult {
|
|
p := &printer{
|
|
symbols: symbols,
|
|
renamer: r,
|
|
importRecords: tree.ImportRecords,
|
|
options: options,
|
|
moduleType: tree.ModuleTypeData.Type,
|
|
exprComments: tree.ExprComments,
|
|
wasLazyExport: tree.HasLazyExport,
|
|
|
|
stmtStart: -1,
|
|
exportDefaultStart: -1,
|
|
arrowExprStart: -1,
|
|
forOfInitStart: -1,
|
|
|
|
prevOpEnd: -1,
|
|
needSpaceBeforeDot: -1,
|
|
prevRegExpEnd: -1,
|
|
noLeadingNewlineHere: -1,
|
|
builder: sourcemap.MakeChunkBuilder(options.InputSourceMap, options.LineOffsetTables, options.ASCIIOnly),
|
|
}
|
|
|
|
if p.exprComments != nil {
|
|
p.printedExprComments = make(map[logger.Loc]bool)
|
|
}
|
|
|
|
p.astHelpers = js_ast.MakeHelperContext(func(ref ast.Ref) bool {
|
|
ref = ast.FollowSymbols(symbols, ref)
|
|
return symbols.Get(ref).Kind == ast.SymbolUnbound
|
|
})
|
|
|
|
// Add the top-level directive if present
|
|
for _, directive := range tree.Directives {
|
|
p.printIndent()
|
|
p.printQuotedUTF8(directive, 0)
|
|
p.print(";")
|
|
p.printNewline()
|
|
}
|
|
|
|
for _, part := range tree.Parts {
|
|
for _, stmt := range part.Stmts {
|
|
p.printStmt(stmt, canOmitStatement)
|
|
p.printSemicolonIfNeeded()
|
|
}
|
|
}
|
|
|
|
result := PrintResult{
|
|
JS: p.js,
|
|
JSONMetadataImports: p.jsonMetadataImports,
|
|
ExtractedLegalComments: p.extractedLegalComments,
|
|
}
|
|
if options.SourceMap != config.SourceMapNone {
|
|
// This is expensive. Only do this if it's necessary.
|
|
result.SourceMapChunk = p.builder.GenerateChunk(p.js)
|
|
}
|
|
return result
|
|
}
|