forked from evanw/esbuild
-
Notifications
You must be signed in to change notification settings - Fork 1
/
renamer.go
274 lines (226 loc) · 7.53 KB
/
renamer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
package bundler
import (
"sort"
"strconv"
"github.com/prantlf/esbuild/internal/ast"
"github.com/prantlf/esbuild/internal/lexer"
)
func computeReservedNames(moduleScopes []*ast.Scope, symbols *ast.SymbolMap) map[string]bool {
names := make(map[string]bool)
// All keywords are reserved names
for k, _ := range lexer.Keywords() {
names[k] = true
}
// All unbound symbols must be reserved names
for _, scope := range moduleScopes {
for _, ref := range scope.Members {
symbol := symbols.Get(ref)
if symbol.Kind == ast.SymbolUnbound {
names[symbol.Name] = true
}
}
}
return names
}
func sortedSymbolsInScope(scope *ast.Scope) uint64Array {
// Sort for determinism
sorted := uint64Array(make([]uint64, 0, len(scope.Members)+len(scope.Generated)))
for _, ref := range scope.Members {
sorted = append(sorted, (uint64(ref.OuterIndex)<<32)|uint64(ref.InnerIndex))
}
for _, ref := range scope.Generated {
sorted = append(sorted, (uint64(ref.OuterIndex)<<32)|uint64(ref.InnerIndex))
}
sort.Sort(sorted)
return sorted
}
////////////////////////////////////////////////////////////////////////////////
// renameAllSymbols() implementation
type renamer struct {
parent *renamer
names map[string]bool
}
func (r *renamer) isNameUsed(name string) bool {
for {
if _, ok := r.names[name]; ok {
return true
}
r = r.parent
if r == nil {
return false
}
}
}
func (r *renamer) findUnusedName(prefix string) string {
name := prefix
tries := 1
for {
if !r.isNameUsed(name) {
r.names[name] = true
return name
}
tries++
name = prefix + strconv.Itoa(tries)
}
}
func renameAllSymbols(reservedNames map[string]bool, moduleScopes []*ast.Scope, symbols *ast.SymbolMap) {
r := &renamer{nil, reservedNames}
alreadyRenamed := make(map[ast.Ref]bool)
// Rename top-level symbols across all files all at once since after
// bundling, they will all be in the same scope
for _, scope := range moduleScopes {
r.renameSymbolsInScope(scope, symbols, alreadyRenamed)
}
// Symbols in child scopes may also have to be renamed to avoid conflicts
for _, scope := range moduleScopes {
for _, child := range scope.Children {
r.renameAllSymbolsRecursive(child, symbols, alreadyRenamed)
}
}
}
func (r *renamer) renameSymbolsInScope(scope *ast.Scope, symbols *ast.SymbolMap, alreadyRenamed map[ast.Ref]bool) {
sorted := sortedSymbolsInScope(scope)
// Rename all symbols in this scope
for _, i := range sorted {
ref := ast.Ref{uint32(i >> 32), uint32(i)}
ref = ast.FollowSymbols(symbols, ref)
// Don't rename the same symbol more than once
if alreadyRenamed[ref] {
continue
}
alreadyRenamed[ref] = true
symbol := symbols.Get(ref)
// Don't rename unbound symbols
if symbol.Kind == ast.SymbolUnbound {
continue
}
symbol.Name = r.findUnusedName(symbol.Name)
symbols.Set(ref, symbol)
}
}
func (parent *renamer) renameAllSymbolsRecursive(scope *ast.Scope, symbols *ast.SymbolMap, alreadyRenamed map[ast.Ref]bool) {
r := &renamer{parent, make(map[string]bool)}
r.renameSymbolsInScope(scope, symbols, alreadyRenamed)
// Symbols in child scopes may also have to be renamed to avoid conflicts
for _, child := range scope.Children {
r.renameAllSymbolsRecursive(child, symbols, alreadyRenamed)
}
}
////////////////////////////////////////////////////////////////////////////////
// minifyAllSymbols() implementation
func minifyAllSymbols(reservedNames map[string]bool, moduleScopes []*ast.Scope, symbols *ast.SymbolMap, nextName int) {
g := minifyGroup{[]uint32{}, make(map[ast.Ref]uint32)}
var next uint32 = 0
// Allocate a slot for every symbol in each top-level scope. These slots must
// not overlap between files because the bundler may smoosh everything
// together into a single scope.
for _, scope := range moduleScopes {
next = g.countSymbolsInScope(scope, symbols, next)
}
// Allocate a slot for every symbol in each nested scope. Since it's
// impossible for symbols from nested scopes to conflict, symbols from
// different nested scopes can reuse the same slots (and therefore get the
// same minified names).
//
// One good heuristic is to merge slots from different nested scopes using
// sequential assignment. Then top-level function statements will always have
// the same argument names, which is better for gzip compression.
for _, scope := range moduleScopes {
for _, child := range scope.Children {
// Deliberately don't update "next" here. Sibling scopes can't collide and
// so can reuse slots.
g.countSymbolsRecursive(child, symbols, next, 0)
}
}
// Sort slot indices descending by the count for that slot
sorted := slotArray(make([]slot, len(g.slotToCount)))
for index, count := range g.slotToCount {
sorted[index] = slot{count, uint32(index)}
}
sort.Sort(sorted)
// Assign names sequentially in order so the most frequent symbols get the
// shortest names
names := make([]string, len(sorted))
for _, slot := range sorted {
name := lexer.NumberToMinifiedName(nextName)
nextName++
// Make sure we never generate a reserved name
for reservedNames[name] {
name = lexer.NumberToMinifiedName(nextName)
nextName++
}
names[slot.index] = name
}
// Copy the names to the appropriate symbols
for ref, i := range g.symbolToSlot {
symbol := symbols.Get(ref)
symbol.Name = names[i]
symbols.Set(ref, symbol)
}
}
type minifyGroup struct {
slotToCount []uint32
symbolToSlot map[ast.Ref]uint32
}
func (g *minifyGroup) countSymbol(slot uint32, ref ast.Ref, count uint32) bool {
// Don't double-count symbols that have already been counted
if _, ok := g.symbolToSlot[ref]; ok {
return false
}
// Optionally extend the slot array
if slot == uint32(len(g.slotToCount)) {
g.slotToCount = append(g.slotToCount, 0)
}
// Count this symbol in this slot
g.slotToCount[slot] += count
g.symbolToSlot[ref] = slot
return true
}
func (g *minifyGroup) countSymbolsInScope(scope *ast.Scope, symbols *ast.SymbolMap, next uint32) uint32 {
sorted := sortedSymbolsInScope(scope)
for _, i := range sorted {
ref := ast.Ref{uint32(i >> 32), uint32(i)}
ref = ast.FollowSymbols(symbols, ref)
symbol := symbols.Get(ref)
// Only count bound symbols
if symbol.Kind == ast.SymbolUnbound {
continue
}
// Add 1 to the count to also include the declaration
if g.countSymbol(next, ref, symbol.UseCountEstimate+1) {
next += 1
}
}
return next
}
func (g *minifyGroup) countSymbolsRecursive(scope *ast.Scope, symbols *ast.SymbolMap, next uint32, labelCount uint32) uint32 {
next = g.countSymbolsInScope(scope, symbols, next)
// Labels are in a separate namespace from symbols
if scope.Kind == ast.ScopeLabel {
symbol := symbols.Get(scope.LabelRef)
g.countSymbol(labelCount, scope.LabelRef, symbol.UseCountEstimate+1)
labelCount += 1
}
for _, child := range scope.Children {
// Deliberately don't update "next" here. Sibling scopes can't collide and
// so can reuse slots.
g.countSymbolsRecursive(child, symbols, next, labelCount)
}
return next
}
type slot struct {
count uint32
index uint32
}
// These types are just so we can use Go's native sort function
type uint64Array []uint64
type slotArray []slot
func (a uint64Array) Len() int { return len(a) }
func (a uint64Array) Swap(i int, j int) { a[i], a[j] = a[j], a[i] }
func (a uint64Array) Less(i int, j int) bool { return a[i] < a[j] }
func (a slotArray) Len() int { return len(a) }
func (a slotArray) Swap(i int, j int) { a[i], a[j] = a[j], a[i] }
func (a slotArray) Less(i int, j int) bool {
ai, aj := a[i], a[j]
return ai.count > aj.count || (ai.count == aj.count && ai.index < aj.index)
}