SandpointsGitHook/vendor/github.com/evanw/esbuild/internal/renamer/renamer.go
2021-03-20 23:21:23 +01:00

536 lines
16 KiB
Go

package renamer
import (
"sort"
"strconv"
"sync"
"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 {
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
}
}
}
return 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
}
type MinifyRenamer struct {
symbols js_ast.SymbolMap
sortedBuffer StableRefArray
reservedNames map[string]uint32
slots [3][]symbolSlot
symbolToSlot 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]),
},
symbolToSlot: 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 == ^uint32(0) {
var ok bool
i, ok = r.symbolToSlot[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
}
}
return r.slots[ns][i].name
}
func (r *MinifyRenamer) AccumulateSymbolUseCounts(symbolUses map[js_ast.Ref]js_ast.SymbolUse, stableSourceIndices []uint32) {
// Sort symbol uses for determinism, reusing a shared memory buffer
r.sortedBuffer = r.sortedBuffer[:0]
for ref := range symbolUses {
r.sortedBuffer = append(r.sortedBuffer, StableRef{
StableOuterIndex: stableSourceIndices[ref.OuterIndex],
Ref: ref,
})
}
sort.Sort(r.sortedBuffer)
// Accumulate symbol use counts
for _, stable := range r.sortedBuffer {
r.AccumulateSymbolCount(stable.Ref, symbolUses[stable.Ref].CountEstimate)
}
}
func (r *MinifyRenamer) AccumulateSymbolCount(ref js_ast.Ref, count uint32) {
// 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
slots := &r.slots[ns]
i := ^symbol.NestedScopeSlot
// If it's not (i.e. it's in a top-level scope), allocate a slot for it
if i == ^uint32(0) {
var ok bool
i, ok = r.symbolToSlot[ref]
if !ok {
i = uint32(len(*slots))
*slots = append(*slots, symbolSlot{})
r.symbolToSlot[ref] = i
}
}
(*slots)[i].count += count
}
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 {
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++
}
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
}
slots[data.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 non-zero 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.
for _, member := range moduleScope.Members {
symbols[member.Ref.InnerIndex].NestedScopeSlot = 1
}
for _, ref := range moduleScope.Generated {
symbols[ref.InnerIndex].NestedScopeSlot = 1
}
// 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 = 0
}
for _, ref := range moduleScope.Generated {
symbols[ref.InnerIndex].NestedScopeSlot = 0
}
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 0 (i.e.
// 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 == 0 {
symbol.NestedScopeSlot = ^slot[ns]
slot[ns]++
}
}
for _, ref := range scope.Generated {
symbol := &symbols[ref.InnerIndex]
if ns := symbol.SlotNamespace(); ns != js_ast.SlotMustNotBeRenamed && symbol.NestedScopeSlot == 0 {
symbol.NestedScopeSlot = ^slot[ns]
slot[ns]++
}
}
// Labels are always declared in a nested scope, so we don't need to check.
if scope.LabelRef != js_ast.InvalidRef {
symbol := &symbols[scope.LabelRef.InnerIndex]
symbol.NestedScopeSlot = ^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)
}
type StableRef struct {
StableOuterIndex uint32
Ref js_ast.Ref
}
// This type is just so we can use Go's native sort function
type StableRefArray []StableRef
func (a StableRefArray) Len() int { return len(a) }
func (a StableRefArray) Swap(i int, j int) { a[i], a[j] = a[j], a[i] }
func (a StableRefArray) Less(i int, j int) bool {
ai, aj := a[i], a[j]
return ai.StableOuterIndex > aj.StableOuterIndex || (ai.StableOuterIndex == aj.StableOuterIndex && ai.Ref.InnerIndex < aj.Ref.InnerIndex)
}
////////////////////////////////////////////////////////////////////////////////
// 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.Outer)),
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.OuterIndex]; 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.OuterIndex]
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
}
// Compute a new name
name := scope.findUnusedName(symbol.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.Outer[ref.OuterIndex]))
r.names[ref.OuterIndex] = 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{OuterIndex: 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
}