Add support for copying files and folders.
This commit is contained in:
624
vendor/github.com/evanw/esbuild/internal/renamer/renamer.go
generated
vendored
Normal file
624
vendor/github.com/evanw/esbuild/internal/renamer/renamer.go
generated
vendored
Normal file
@ -0,0 +1,624 @@
|
||||
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
|
||||
}
|
Reference in New Issue
Block a user