gowebbuild/vendor/github.com/evanw/esbuild/internal/renamer/renamer.go

625 lines
19 KiB
Go
Raw Normal View History

package renamer
import (
"fmt"
"sort"
"strconv"
"sync"
"sync/atomic"
"github.com/evanw/esbuild/internal/ast"
"github.com/evanw/esbuild/internal/js_ast"
"github.com/evanw/esbuild/internal/js_lexer"
)
func ComputeReservedNames(moduleScopes []*js_ast.Scope, symbols js_ast.SymbolMap) map[string]uint32 {
names := make(map[string]uint32)
// All keywords and strict mode reserved words are reserved names
for k := range js_lexer.Keywords {
names[k] = 1
}
for k := range js_lexer.StrictModeReservedWords {
names[k] = 1
}
// All unbound symbols must be reserved names
for _, scope := range moduleScopes {
computeReservedNamesForScope(scope, symbols, names)
}
return names
}
func computeReservedNamesForScope(scope *js_ast.Scope, symbols js_ast.SymbolMap, names map[string]uint32) {
for _, member := range scope.Members {
symbol := symbols.Get(member.Ref)
if symbol.Kind == js_ast.SymbolUnbound || symbol.MustNotBeRenamed {
names[symbol.OriginalName] = 1
}
}
for _, ref := range scope.Generated {
symbol := symbols.Get(ref)
if symbol.Kind == js_ast.SymbolUnbound || symbol.MustNotBeRenamed {
names[symbol.OriginalName] = 1
}
}
// If there's a direct "eval" somewhere inside the current scope, continue
// traversing down the scope tree until we find it to get all reserved names
if scope.ContainsDirectEval {
for _, child := range scope.Children {
if child.ContainsDirectEval {
computeReservedNamesForScope(child, symbols, names)
}
}
}
}
type Renamer interface {
NameForSymbol(ref js_ast.Ref) string
}
////////////////////////////////////////////////////////////////////////////////
// noOpRenamer
type noOpRenamer struct {
symbols js_ast.SymbolMap
}
func NewNoOpRenamer(symbols js_ast.SymbolMap) Renamer {
return &noOpRenamer{
symbols: symbols,
}
}
func (r *noOpRenamer) NameForSymbol(ref js_ast.Ref) string {
ref = js_ast.FollowSymbols(r.symbols, ref)
return r.symbols.Get(ref).OriginalName
}
////////////////////////////////////////////////////////////////////////////////
// MinifyRenamer
type symbolSlot struct {
name string
count uint32
needsCapitalForJSX uint32 // This is really a bool but needs to be atomic
}
type MinifyRenamer struct {
symbols js_ast.SymbolMap
reservedNames map[string]uint32
slots [3][]symbolSlot
topLevelSymbolToSlot map[js_ast.Ref]uint32
}
func NewMinifyRenamer(symbols js_ast.SymbolMap, firstTopLevelSlots js_ast.SlotCounts, reservedNames map[string]uint32) *MinifyRenamer {
return &MinifyRenamer{
symbols: symbols,
reservedNames: reservedNames,
slots: [3][]symbolSlot{
make([]symbolSlot, firstTopLevelSlots[0]),
make([]symbolSlot, firstTopLevelSlots[1]),
make([]symbolSlot, firstTopLevelSlots[2]),
},
topLevelSymbolToSlot: make(map[js_ast.Ref]uint32),
}
}
func (r *MinifyRenamer) NameForSymbol(ref js_ast.Ref) string {
// Follow links to get to the underlying symbol
ref = js_ast.FollowSymbols(r.symbols, ref)
symbol := r.symbols.Get(ref)
// Skip this symbol if the name is pinned
ns := symbol.SlotNamespace()
if ns == js_ast.SlotMustNotBeRenamed {
return symbol.OriginalName
}
// Check if it's a nested scope symbol
i := symbol.NestedScopeSlot
// If it's not (i.e. it's in a top-level scope), look up the slot
if !i.IsValid() {
index, ok := r.topLevelSymbolToSlot[ref]
if !ok {
// If we get here, then we're printing a symbol that never had any
// recorded uses. This is odd but can happen in certain scenarios.
// For example, code in a branch with dead control flow won't mark
// any uses but may still be printed. In that case it doesn't matter
// what name we use since it's dead code.
return symbol.OriginalName
}
i = ast.MakeIndex32(index)
}
return r.slots[ns][i.GetIndex()].name
}
// The sort order here is arbitrary but needs to be consistent between builds.
// The InnerIndex should be stable because the parser for a single file is
// single-threaded and deterministically assigns out InnerIndex values
// sequentially. But the SourceIndex should be unstable because the main thread
// assigns out source index values sequentially to newly-discovered dependencies
// in a multi-threaded producer/consumer relationship. So instead we use the
// index of the source in the DFS order over all entry points for stability.
type DeferredTopLevelSymbol struct {
StableSourceIndex uint32
Ref js_ast.Ref
Count uint32
}
// This type is just so we can use Go's native sort function
type DeferredTopLevelSymbolArray []DeferredTopLevelSymbol
func (a DeferredTopLevelSymbolArray) Len() int { return len(a) }
func (a DeferredTopLevelSymbolArray) Swap(i int, j int) { a[i], a[j] = a[j], a[i] }
func (a DeferredTopLevelSymbolArray) Less(i int, j int) bool {
ai, aj := a[i], a[j]
if ai.StableSourceIndex < aj.StableSourceIndex {
return true
}
if ai.StableSourceIndex > aj.StableSourceIndex {
return false
}
if ai.Ref.InnerIndex < aj.Ref.InnerIndex {
return true
}
if ai.Ref.InnerIndex > aj.Ref.InnerIndex {
return false
}
return ai.Count < aj.Count
}
func (r *MinifyRenamer) AccumulateSymbolUseCounts(
topLevelSymbols *DeferredTopLevelSymbolArray,
symbolUses map[js_ast.Ref]js_ast.SymbolUse,
stableSourceIndices []uint32,
) {
// NOTE: This function is run in parallel. Make sure to avoid data races.
for ref, use := range symbolUses {
r.AccumulateSymbolCount(topLevelSymbols, ref, use.CountEstimate, stableSourceIndices)
}
}
func (r *MinifyRenamer) AccumulateSymbolCount(
topLevelSymbols *DeferredTopLevelSymbolArray,
ref js_ast.Ref,
count uint32,
stableSourceIndices []uint32,
) {
// NOTE: This function is run in parallel. Make sure to avoid data races.
// Follow links to get to the underlying symbol
ref = js_ast.FollowSymbols(r.symbols, ref)
symbol := r.symbols.Get(ref)
for symbol.NamespaceAlias != nil {
ref = js_ast.FollowSymbols(r.symbols, symbol.NamespaceAlias.NamespaceRef)
symbol = r.symbols.Get(ref)
}
// Skip this symbol if the name is pinned
ns := symbol.SlotNamespace()
if ns == js_ast.SlotMustNotBeRenamed {
return
}
// Check if it's a nested scope symbol
if i := symbol.NestedScopeSlot; i.IsValid() {
// If it is, accumulate the count using a parallel-safe atomic increment
slot := &r.slots[ns][i.GetIndex()]
atomic.AddUint32(&slot.count, count)
if symbol.MustStartWithCapitalLetterForJSX {
atomic.StoreUint32(&slot.needsCapitalForJSX, 1)
}
return
}
// If it's a top-level symbol, defer it to later since we have
// to allocate slots for these in serial instead of in parallel
*topLevelSymbols = append(*topLevelSymbols, DeferredTopLevelSymbol{
StableSourceIndex: stableSourceIndices[ref.SourceIndex],
Ref: ref,
Count: count,
})
}
// The parallel part of the symbol count accumulation algorithm above processes
// nested symbols and generates on an array of top-level symbols to process later.
// After the parallel part has finished, that array of top-level symbols is passed
// to this function which processes them in serial.
func (r *MinifyRenamer) AllocateTopLevelSymbolSlots(topLevelSymbols DeferredTopLevelSymbolArray) {
for _, stable := range topLevelSymbols {
symbol := r.symbols.Get(stable.Ref)
slots := &r.slots[symbol.SlotNamespace()]
if i, ok := r.topLevelSymbolToSlot[stable.Ref]; ok {
slot := &(*slots)[i]
slot.count += stable.Count
if symbol.MustStartWithCapitalLetterForJSX {
slot.needsCapitalForJSX = 1
}
} else {
needsCapitalForJSX := uint32(0)
if symbol.MustStartWithCapitalLetterForJSX {
needsCapitalForJSX = 1
}
i = uint32(len(*slots))
*slots = append(*slots, symbolSlot{
count: stable.Count,
needsCapitalForJSX: needsCapitalForJSX,
})
r.topLevelSymbolToSlot[stable.Ref] = i
}
}
}
func (r *MinifyRenamer) AssignNamesByFrequency(minifier *js_ast.NameMinifier) {
for ns, slots := range r.slots {
// Sort symbols by count
sorted := make(slotAndCountArray, len(slots))
for i, item := range slots {
sorted[i] = slotAndCount{slot: uint32(i), count: item.count}
}
sort.Sort(sorted)
// Assign names to symbols
nextName := 0
for _, data := range sorted {
slot := &slots[data.slot]
name := minifier.NumberToMinifiedName(nextName)
nextName++
// Make sure we never generate a reserved name. We only have to worry
// about collisions with reserved identifiers for normal symbols, and we
// only have to worry about collisions with keywords for labels. We do
// not have to worry about either for private names because they start
// with a "#" character.
switch js_ast.SlotNamespace(ns) {
case js_ast.SlotDefault:
for r.reservedNames[name] != 0 {
name = minifier.NumberToMinifiedName(nextName)
nextName++
}
// Make sure names of symbols used in JSX elements start with a capital letter
if slot.needsCapitalForJSX != 0 {
for name[0] >= 'a' && name[0] <= 'z' {
name = minifier.NumberToMinifiedName(nextName)
nextName++
}
}
case js_ast.SlotLabel:
for js_lexer.Keywords[name] != 0 {
name = minifier.NumberToMinifiedName(nextName)
nextName++
}
}
// Private names must be prefixed with "#"
if js_ast.SlotNamespace(ns) == js_ast.SlotPrivateName {
name = "#" + name
}
slot.name = name
}
}
}
// Returns the number of nested slots
func AssignNestedScopeSlots(moduleScope *js_ast.Scope, symbols []js_ast.Symbol) (slotCounts js_ast.SlotCounts) {
// Temporarily set the nested scope slots of top-level symbols to valid so
// they aren't renamed in nested scopes. This prevents us from accidentally
// assigning nested scope slots to variables declared using "var" in a nested
// scope that are actually hoisted up to the module scope to become a top-
// level symbol.
validSlot := ast.MakeIndex32(1)
for _, member := range moduleScope.Members {
symbols[member.Ref.InnerIndex].NestedScopeSlot = validSlot
}
for _, ref := range moduleScope.Generated {
symbols[ref.InnerIndex].NestedScopeSlot = validSlot
}
// Assign nested scope slots independently for each nested scope
for _, child := range moduleScope.Children {
slotCounts.UnionMax(assignNestedScopeSlotsHelper(child, symbols, js_ast.SlotCounts{}))
}
// Then set the nested scope slots of top-level symbols back to zero. Top-
// level symbols are not supposed to have nested scope slots.
for _, member := range moduleScope.Members {
symbols[member.Ref.InnerIndex].NestedScopeSlot = ast.Index32{}
}
for _, ref := range moduleScope.Generated {
symbols[ref.InnerIndex].NestedScopeSlot = ast.Index32{}
}
return
}
func assignNestedScopeSlotsHelper(scope *js_ast.Scope, symbols []js_ast.Symbol, slot js_ast.SlotCounts) js_ast.SlotCounts {
// Sort member map keys for determinism
sortedMembers := make([]int, 0, len(scope.Members))
for _, member := range scope.Members {
sortedMembers = append(sortedMembers, int(member.Ref.InnerIndex))
}
sort.Ints(sortedMembers)
// Assign slots for this scope's symbols. Only do this if the slot is
// not already assigned. Nested scopes have copies of symbols from parent
// scopes and we want to use the slot from the parent scope, not child scopes.
for _, innerIndex := range sortedMembers {
symbol := &symbols[innerIndex]
if ns := symbol.SlotNamespace(); ns != js_ast.SlotMustNotBeRenamed && !symbol.NestedScopeSlot.IsValid() {
symbol.NestedScopeSlot = ast.MakeIndex32(slot[ns])
slot[ns]++
}
}
for _, ref := range scope.Generated {
symbol := &symbols[ref.InnerIndex]
if ns := symbol.SlotNamespace(); ns != js_ast.SlotMustNotBeRenamed && !symbol.NestedScopeSlot.IsValid() {
symbol.NestedScopeSlot = ast.MakeIndex32(slot[ns])
slot[ns]++
}
}
// Labels are always declared in a nested scope, so we don't need to check.
if scope.Label.Ref != js_ast.InvalidRef {
symbol := &symbols[scope.Label.Ref.InnerIndex]
symbol.NestedScopeSlot = ast.MakeIndex32(slot[js_ast.SlotLabel])
slot[js_ast.SlotLabel]++
}
// Assign slots for the symbols of child scopes
slotCounts := slot
for _, child := range scope.Children {
slotCounts.UnionMax(assignNestedScopeSlotsHelper(child, symbols, slot))
}
return slotCounts
}
type slotAndCount struct {
slot uint32
count uint32
}
// This type is just so we can use Go's native sort function
type slotAndCountArray []slotAndCount
func (a slotAndCountArray) Len() int { return len(a) }
func (a slotAndCountArray) Swap(i int, j int) { a[i], a[j] = a[j], a[i] }
func (a slotAndCountArray) Less(i int, j int) bool {
ai, aj := a[i], a[j]
return ai.count > aj.count || (ai.count == aj.count && ai.slot < aj.slot)
}
////////////////////////////////////////////////////////////////////////////////
// NumberRenamer
type NumberRenamer struct {
symbols js_ast.SymbolMap
names [][]string
root numberScope
}
func NewNumberRenamer(symbols js_ast.SymbolMap, reservedNames map[string]uint32) *NumberRenamer {
return &NumberRenamer{
symbols: symbols,
names: make([][]string, len(symbols.SymbolsForSource)),
root: numberScope{nameCounts: reservedNames},
}
}
func (r *NumberRenamer) NameForSymbol(ref js_ast.Ref) string {
ref = js_ast.FollowSymbols(r.symbols, ref)
if inner := r.names[ref.SourceIndex]; inner != nil {
if name := inner[ref.InnerIndex]; name != "" {
return name
}
}
return r.symbols.Get(ref).OriginalName
}
func (r *NumberRenamer) AddTopLevelSymbol(ref js_ast.Ref) {
r.assignName(&r.root, ref)
}
func (r *NumberRenamer) assignName(scope *numberScope, ref js_ast.Ref) {
ref = js_ast.FollowSymbols(r.symbols, ref)
// Don't rename the same symbol more than once
inner := r.names[ref.SourceIndex]
if inner != nil && inner[ref.InnerIndex] != "" {
return
}
// Don't rename unbound symbols, symbols marked as reserved names, labels, or private names
symbol := r.symbols.Get(ref)
if symbol.SlotNamespace() != js_ast.SlotDefault {
return
}
// Make sure names of symbols used in JSX elements start with a capital letter
originalName := symbol.OriginalName
if symbol.MustStartWithCapitalLetterForJSX {
if first := rune(originalName[0]); first >= 'a' && first <= 'z' {
originalName = fmt.Sprintf("%c%s", first+('A'-'a'), originalName[1:])
}
}
// Compute a new name
name := scope.findUnusedName(originalName)
// Store the new name
if inner == nil {
// Note: This should not be a data race even though this method is run from
// multiple threads. The parallel part only looks at symbols defined in
// nested scopes, and those can only ever be accessed from within the file.
// References to those symbols should never spread across files.
//
// While we could avoid the data race by densely preallocating the entire
// "names" array ahead of time, that will waste a lot more memory for
// builds that make heavy use of code splitting and have many chunks. Doing
// things lazily like this means we use less memory but still stay safe.
inner = make([]string, len(r.symbols.SymbolsForSource[ref.SourceIndex]))
r.names[ref.SourceIndex] = inner
}
inner[ref.InnerIndex] = name
}
func (r *NumberRenamer) assignNamesRecursive(scope *js_ast.Scope, sourceIndex uint32, parent *numberScope, sorted *[]int) {
s := &numberScope{parent: parent, nameCounts: make(map[string]uint32)}
// Sort member map keys for determinism, reusing a shared memory buffer
*sorted = (*sorted)[:0]
for _, member := range scope.Members {
*sorted = append(*sorted, int(member.Ref.InnerIndex))
}
sort.Ints(*sorted)
// Rename all symbols in this scope
for _, innerIndex := range *sorted {
r.assignName(s, js_ast.Ref{SourceIndex: sourceIndex, InnerIndex: uint32(innerIndex)})
}
for _, ref := range scope.Generated {
r.assignName(s, ref)
}
// Symbols in child scopes may also have to be renamed to avoid conflicts
for _, child := range scope.Children {
r.assignNamesRecursive(child, sourceIndex, s, sorted)
}
}
func (r *NumberRenamer) AssignNamesByScope(nestedScopes map[uint32][]*js_ast.Scope) {
waitGroup := sync.WaitGroup{}
waitGroup.Add(len(nestedScopes))
// Rename nested scopes from separate files in parallel
for sourceIndex, scopes := range nestedScopes {
go func(sourceIndex uint32, scopes []*js_ast.Scope) {
var sorted []int
for _, scope := range scopes {
r.assignNamesRecursive(scope, sourceIndex, &r.root, &sorted)
}
waitGroup.Done()
}(sourceIndex, scopes)
}
waitGroup.Wait()
}
type numberScope struct {
parent *numberScope
// This is used as a set of used names in this scope. This also maps the name
// to the number of times the name has experienced a collision. When a name
// collides with an already-used name, we need to rename it. This is done by
// incrementing a number at the end until the name is unused. We save the
// count here so that subsequent collisions can start counting from where the
// previous collision ended instead of having to start counting from 1.
nameCounts map[string]uint32
}
type nameUse uint8
const (
nameUnused nameUse = iota
nameUsed
nameUsedInSameScope
)
func (s *numberScope) findNameUse(name string) nameUse {
original := s
for {
if _, ok := s.nameCounts[name]; ok {
if s == original {
return nameUsedInSameScope
}
return nameUsed
}
s = s.parent
if s == nil {
return nameUnused
}
}
}
func (s *numberScope) findUnusedName(name string) string {
name = js_lexer.ForceValidIdentifier(name)
if use := s.findNameUse(name); use != nameUnused {
// If the name is already in use, generate a new name by appending a number
tries := uint32(1)
if use == nameUsedInSameScope {
// To avoid O(n^2) behavior, the number must start off being the number
// that we used last time there was a collision with this name. Otherwise
// if there are many collisions with the same name, each name collision
// would have to increment the counter past all previous name collisions
// which is a O(n^2) time algorithm. Only do this if this symbol comes
// from the same scope as the previous one since sibling scopes can reuse
// the same name without problems.
tries = s.nameCounts[name]
}
prefix := name
// Keep incrementing the number until the name is unused
for {
tries++
name = prefix + strconv.Itoa(int(tries))
// Make sure this new name is unused
if s.findNameUse(name) == nameUnused {
// Store the count so we can start here next time instead of starting
// from 1. This means we avoid O(n^2) behavior.
if use == nameUsedInSameScope {
s.nameCounts[prefix] = tries
}
break
}
}
}
// Each name starts off with a count of 1 so that the first collision with
// "name" is called "name2"
s.nameCounts[name] = 1
return name
}
////////////////////////////////////////////////////////////////////////////////
// ExportRenamer
type ExportRenamer struct {
count int
used map[string]uint32
}
func (r *ExportRenamer) NextRenamedName(name string) string {
if r.used == nil {
r.used = make(map[string]uint32)
}
if tries, ok := r.used[name]; ok {
prefix := name
for {
tries++
name = prefix + strconv.Itoa(int(tries))
if _, ok := r.used[name]; !ok {
break
}
}
r.used[name] = tries
} else {
r.used[name] = 1
}
return name
}
func (r *ExportRenamer) NextMinifiedName() string {
name := js_ast.DefaultNameMinifier.NumberToMinifiedName(r.count)
r.count++
return name
}