625 lines
19 KiB
Go
625 lines
19 KiB
Go
|
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
|
||
|
}
|