func CanEscapeIdentifier(name string, unsupportedJSFeatures compat.JSFeature, asciiOnly bool) bool {
	return js_lexer.IsIdentifierES5AndESNext(name) && (!asciiOnly ||
		!unsupportedJSFeatures.Has(compat.UnicodeEscapes) ||
		!js_lexer.ContainsNonBMPCodePoint(name))
}
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.
package js_printer
import (
	"bytes"
	"fmt"
	"math"
	"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/js_ast"
	"github.com/evanw/esbuild/internal/js_lexer"
	"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 canPrintWithoutEscape(c rune, asciiOnly bool) bool {
	if c <= lastASCII {
		return c >= firstASCII && c != '\\' && c != '"'
	} else {
		return !asciiOnly && c != '\uFEFF' && (c < firstHighSurrogate || c > lastLowSurrogate)
	}
}
func QuoteForJSON(text string, asciiOnly bool) []byte {
	lenEstimate := 2
	for _, c := range text {
		if canPrintWithoutEscape(c, asciiOnly) {
			lenEstimate += utf8.RuneLen(c)
		} else {
			switch c {
			case '\b', '\f', '\n', '\r', '\t', '\\', '"':
				lenEstimate += 2
			default:
				if c <= 0xFFFF {
					lenEstimate += 6
				} else {
					lenEstimate += 12
				}
			}
		}
	}
	bytes := make([]byte, 0, lenEstimate)
	i := 0
	n := len(text)
	bytes = append(bytes, '"')
	for i < n {
		c, width := js_lexer.DecodeWTF8Rune(text[i:])
		if canPrintWithoutEscape(c, asciiOnly) {
			start := i
			i += width
			for i < n {
				c, width = js_lexer.DecodeWTF8Rune(text[i:])
				if !canPrintWithoutEscape(c, asciiOnly) {
					break
				}
				i += width
			}
			bytes = append(bytes, text[start:i]...)
			continue
		}
		switch c {
		case '\b':
			bytes = append(bytes, "\\b"...)
			i++
		case '\f':
			bytes = append(bytes, "\\f"...)
			i++
		case '\n':
			bytes = append(bytes, "\\n"...)
			i++
		case '\r':
			bytes = append(bytes, "\\r"...)
			i++
		case '\t':
			bytes = append(bytes, "\\t"...)
			i++
		case '\\':
			bytes = append(bytes, "\\\\"...)
			i++
		case '"':
			bytes = append(bytes, "\\\""...)
			i++
		default:
			i += width
			if c <= 0xFFFF {
				bytes = append(
					bytes,
					'\\', 'u', hexChars[c>>12], hexChars[(c>>8)&15], hexChars[(c>>4)&15], hexChars[c&15],
				)
			} else {
				c -= 0x10000
				lo := firstHighSurrogate + ((c >> 10) & 0x3FF)
				hi := firstLowSurrogate + (c & 0x3FF)
				bytes = append(
					bytes,
					'\\', 'u', hexChars[lo>>12], hexChars[(lo>>8)&15], hexChars[(lo>>4)&15], hexChars[lo&15],
					'\\', 'u', hexChars[hi>>12], hexChars[(hi>>8)&15], hexChars[(hi>>4)&15], hexChars[hi&15],
				)
			}
		}
	}
	return append(bytes, '"')
}
func QuoteIdentifier(js []byte, name string, unsupportedFeatures compat.JSFeature) []byte {
	isASCII := false
	asciiStart := 0
	for i, c := range name {
		if c >= firstASCII && c <= lastASCII {
			if !isASCII {
				isASCII = true
				asciiStart = i
			}
		} else {
			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 {
		js = append(js, name[asciiStart:]...)
	}
	return js
}
func (p *printer) printUnquotedUTF16(text []uint16, quote rune) {
	temp := make([]byte, utf8.UTFMax)
	js := p.js
	i := 0
	n := len(text)
	for i < n {
		c := text[i]
		i++
		switch c {
		case '\x00':
			if i < n && text[i] >= '0' && text[i] <= '9' {
				js = append(js, "\\x00"...)
			} else {
				js = append(js, "\\0"...)
			}
		case '\x07':
			js = append(js, "\\x07"...)
		case '\b':
			js = append(js, "\\b"...)
		case '\f':
			js = append(js, "\\f"...)
		case '\n':
			if quote == '`' {
				js = append(js, '\n')
			} else {
				js = append(js, "\\n"...)
			}
		case '\r':
			js = append(js, "\\r"...)
		case '\v':
			js = append(js, "\\v"...)
		case '\\':
			js = append(js, "\\\\"...)
		case '/':
			if 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 {
			case c <= lastASCII:
				js = append(js, byte(c))
			case c >= firstHighSurrogate && c <= lastHighSurrogate:
				if i < n {
					c2 := text[i]
					if c2 >= firstLowSurrogate && c2 <= lastLowSurrogate {
						r := (rune(c) << 10) + rune(c2) + (0x10000 - (firstHighSurrogate << 10) - firstLowSurrogate)
						i++
						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
						}
						width := utf8.EncodeRune(temp, r)
						js = append(js, temp[:width]...)
						continue
					}
				}
				js = append(js, '\\', 'u', hexChars[c>>12], hexChars[(c>>8)&15], hexChars[(c>>4)&15], hexChars[c&15])
			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])
			case p.options.ASCIIOnly:
				js = append(js, '\\', 'x', hexChars[c>>4], hexChars[c&15])
			default:
				width := utf8.EncodeRune(temp, rune(c))
				js = append(js, temp[:width]...)
			}
		}
	}
	p.js = js
}
func (p *printer) canPrintTextAsJSXAttribute(text []uint16) (quote string, ok bool) {
	single := true
	double := true
	for _, c := range text {
		if c < firstASCII {
			return "", false
		}
		if p.options.ASCIIOnly && c > lastASCII {
			return "", false
		}
		switch c {
		case '&':
			return "", false
		case '"':
			double = false
			if !single {
				break
			}
		case '\'':
			single = false
			if !double {
				break
			}
		}
	}
	if double {
		return "\"", true
	}
	if single {
		return "'", true
	}
	return "", false
}
func (p *printer) canPrintTextAsJSXChild(text []uint16) bool {
	for _, c := range text {
		if c < firstASCII {
			return false
		}
		if p.options.ASCIIOnly && c > lastASCII {
			return false
		}
		switch c {
		case '&', '<', '>', '{', '}':
			return false
		}
	}
	return true
}
func (p *printer) printJSXTag(tagOrNil js_ast.Expr) {
	switch e := tagOrNil.Data.(type) {
	case *js_ast.EString:
		p.addSourceMapping(tagOrNil.Loc)
		p.print(js_lexer.UTF16ToString(e.Value))
	case *js_ast.EIdentifier:
		name := p.renamer.NameForSymbol(e.Ref)
		p.addSourceMapping(tagOrNil.Loc)
		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                js_ast.SymbolMap
	renamer                renamer.Renamer
	importRecords          []ast.ImportRecord
	options                Options
	extractedLegalComments map[string]bool
	needsSemicolon         bool
	js                     []byte
	stmtStart              int
	exportDefaultStart     int
	arrowExprStart         int
	forOfInitStart         int
	prevOp                 js_ast.OpCode
	prevOpEnd              int
	prevNumEnd             int
	prevRegExpEnd          int
	callTarget             js_ast.E
	intToBytesBuffer       [64]byte
	builder                sourcemap.ChunkBuilder
}
func (p *printer) print(text string) {
	p.js = append(p.js, text...)
}
func (p *printer) printBytes(bytes []byte) {
	p.js = append(p.js, bytes...)
}
func (p *printer) printQuotedUTF8(text string, allowBacktick bool) {
	p.printQuotedUTF16(js_lexer.StringToUTF16(text), allowBacktick)
}
func (p *printer) addSourceMapping(loc logger.Loc) {
	if p.options.AddSourceMappings {
		p.builder.AddSourceMapping(loc, p.js)
	}
}
func (p *printer) printIndent() {
	if !p.options.RemoveWhitespace {
		for i := 0; i < p.options.Indent; i++ {
			p.print("  ")
		}
	}
}
func (p *printer) printSymbol(ref js_ast.Ref) {
	name := p.renamer.NameForSymbol(ref)
	if !strings.HasPrefix(name, "#") {
		p.printSpaceBeforeIdentifier()
	}
	p.printIdentifier(name)
}
func (p *printer) printClauseAlias(alias string) {
	if js_lexer.IsIdentifier(alias) {
		p.printSpaceBeforeIdentifier()
		p.printIdentifier(alias)
	} else {
		p.printQuotedUTF8(alias, false  )
	}
}